          for item in self.get_item():
              if item == "EOF":
                  self.finalize(item)
                  break
              image = self.convert_color(item["image"])
  
              logger.trace("Aligning faces")
              try:
                  item["landmarks"] = self.process_landmarks(image, item["detected_faces"])
                  logger.trace("Aligned faces: %s", item["landmarks"])
              except ValueError as err:
                  logger.warning("Image '%s' could not be processed. This may be due to corrupted "
                                 "data: %s", item["filename"], str(err))
                  item["detected_faces"] = list()
                  item["landmarks"] = list()
                  # UNCOMMENT THIS CODE BLOCK TO PRINT TRACEBACK ERRORS
                  # import sys
                  # exc_info = sys.exc_info()
                  # traceback.print_exception(*exc_info)
              self.finalize(item)
          logger.debug("Completed Align")
  
      def convert_color(self, image):
          """ Convert the image to the correct colorspace """
          logger.trace("Converting image to colorspace: %s", self.colorspace)
          if self.colorspace == "RGB":
              cvt_image = image[:, :, ::-1].copy()
          elif self.colorspace == "GRAY":
              cvt_image = cv2.cvtColor(image.copy(), cv2.COLOR_BGR2GRAY)  # pylint:disable=no-member
          else:
              cvt_image = image.copy()
          return cvt_image
  
      def process_landmarks(self, image, detected_faces):
 
</s> remove             cvt_image = image.copy()
        return cvt_image

    def process_landmarks(self, image, detected_faces):
        """ Align image and process landmarks """
        logger.trace("Processing landmarks")
        retval = list()
        for detected_face in detected_faces:
            feed_dict = self.align_image(detected_face, image)
            self.normalize_face(feed_dict)
            landmarks = self.predict_landmarks(feed_dict)
            retval.append(landmarks)
        logger.trace("Processed landmarks: %s", retval)
        return retval
 </s> add             logger.trace(item)
        return exhausted, batch

    def _collect_item(self, queue):
        """ Collect the item from the _rollover dict or from the queue
            Add face count per frame to self._faces_per_filename for joining
            batches back up in finalize """
        if self._rollover:
            logger.trace("Getting from _rollover: (filename: `%s`, faces: %s)",
                         self._rollover["filename"], len(self._rollover["detected_faces"]))
            item = self._rollover
            self._rollover = dict()
        else:
            item = self._get_item(queue)
            if item != "EOF":
                logger.trace("Getting from queue: (filename: %s, faces: %s)",
                             item["filename"], len(item["detected_faces"]))
                self._faces_per_filename[item["filename"]] = len(item["detected_faces"])
        return item

    # <<< FINALIZE METHODS >>> #
    def finalize(self, batch):
        """ Finalize the output from Aligner

        This should be called as the final task of each `plugin`.

        It strips unneeded items from the :attr:`batch` ``dict`` and pairs the detected faces back
        up with their original frame before yielding each frame.

        Outputs items in the format:

        >>> {'image': [<original frame>],
        >>>  'filename': [<frame filename>),
        >>>  'detected_faces': [<lib.faces_detect.DetectedFace objects>]}

        Parameters
        ----------
        batch : dict
            The final ``dict`` from the `plugin` process. It must contain the `keys`:
            ``detected_faces``, ``landmarks``, ``filename``, ``image``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and list of
            :class:`lib.faces_detect.DetectedFace` objects.

        """

        for face, landmarks in zip(batch["detected_faces"], batch["landmarks"]):
            face.landmarks_xy = [(int(round(pt[0])), int(round(pt[1]))) for pt in landmarks]

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        logger.trace("Item out: %s", {key: val
                                      for key, val in batch.items()
                                      if key != "image"})
        for filename, image, face in zip(batch["filename"],
                                         batch["image"],
                                         batch["detected_faces"]):
            self._output_faces.append(face)
            if len(self._output_faces) != self._faces_per_filename[filename]:
                continue
            retval = dict(filename=filename, image=image, detected_faces=self._output_faces)

            self._output_faces = []
            logger.trace("Yielding: (filename: '%s', image: %s, detected_faces: %s)",
                         retval["filename"], retval["image"].shape, len(retval["detected_faces"]))
            yield retval

    # <<< PROTECTED METHODS >>> #
    # <<< PREDICT WRAPPER >>> #
    def _predict(self, batch):
        """ Just return the aligner's predict function """
        return self.predict(batch) </s> remove                 self.finalize(item)
 </s> add                 logger.trace("EOF received")
                exhausted = True </s> remove     def initialize(self, *args, **kwargs):
        """ Calculate batch size """
        super().initialize(*args, **kwargs)
        logger.info("Initializing cv2 DNN Detector...")
        logger.verbose("Using CPU for detection")
        self.detector = cv2.dnn.readNetFromCaffe(self.model_path[1],  # pylint: disable=no-member
                                                 self.model_path[0])
        self.detector.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)  # pylint: disable=no-member
        self.init.set()
        logger.info("Initialized cv2 DNN Detector.")

    def detect_faces(self, *args, **kwargs):
        """ Detect faces in grayscale image """
        super().detect_faces(*args, **kwargs)
        while True:
            item = self.get_item()
            if item == "EOF":
                break
            logger.trace("Detecting faces: %s", item["filename"])
            [detect_image, scale] = self.compile_detection_image(item["image"],
                                                                 is_square=True,
                                                                 scale_up=True)
            height, width = detect_image.shape[:2]
            for angle in self.rotation:
                current_image, rotmat = self.rotate_image(detect_image, angle)
                logger.trace("Detecting faces")

                blob = cv2.dnn.blobFromImage(current_image,  # pylint: disable=no-member
                                             1.0,
                                             self.target,
                                             [104, 117, 123],
                                             False,
                                             False)
                self.detector.setInput(blob)
                detected = self.detector.forward()
                faces = list()
                for i in range(detected.shape[2]):
                    confidence = detected[0, 0, i, 2]
                    if confidence >= self.confidence:
                        logger.trace("Accepting due to confidence %s >= %s",
                                     confidence, self.confidence)
                        faces.append([(detected[0, 0, i, 3] * width),
                                      (detected[0, 0, i, 4] * height),
                                      (detected[0, 0, i, 5] * width),
                                      (detected[0, 0, i, 6] * height)])

                logger.trace("Detected faces: %s", [face for face in faces])

                if angle != 0 and faces:
                    logger.verbose("found face(s) by rotating image %s degrees", angle)

                if faces:
                    break

            detected_faces = self.process_output(faces, rotmat, scale)
            item["detected_faces"] = detected_faces
            self.finalize(item)

        self.queues["out"].put("EOF")
        logger.debug("Detecting Faces Complete")

    def process_output(self, faces, rotation_matrix, scale):
 </s> add     def init_model(self):
        """ Initialize CV2 DNN Detector Model"""
        self.model = cv2.dnn.readNetFromCaffe(self.model_path[1],  # pylint: disable=no-member
                                              self.model_path[0])
        self.model.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)  # pylint: disable=no-member

    def process_input(self, batch):
        """ Compile the detection image(s) for prediction """
        batch["feed"] = cv2.dnn.blobFromImages(batch["scaled_image"],  # pylint: disable=no-member
                                               scalefactor=1.0,
                                               size=(self.input_size, self.input_size),
                                               mean=[104, 117, 123],
                                               swapRB=False,
                                               crop=False)
        return batch

    def predict(self, batch):
        """ Run model to get predictions """
        self.model.setInput(batch["feed"])
        predictions = self.model.forward()
        batch["prediction"] = self.finalize_predictions(predictions)
        return batch

    def finalize_predictions(self, predictions):
        """ Filter faces based on confidence level """
        faces = list()
        for i in range(predictions.shape[2]):
            confidence = predictions[0, 0, i, 2]
            if confidence >= self.confidence:
                logger.trace("Accepting due to confidence %s >= %s",
                             confidence, self.confidence)
                faces.append([(predictions[0, 0, i, 3] * self.input_size),
                              (predictions[0, 0, i, 4] * self.input_size),
                              (predictions[0, 0, i, 5] * self.input_size),
                              (predictions[0, 0, i, 6] * self.input_size)])
        logger.trace("faces: %s", faces)
        return [np.array(faces)]

    def process_output(self, batch): </s> remove                                (rotated_width, rotated_height)),
                rotation_matrix)

    # << QUEUE METHODS >> #
    def get_item(self):
        """ Yield one item from the queue """
        item = self.queues["in"].get()
        if isinstance(item, dict):
            logger.trace("Item in: %s", item["filename"])
        else:
            logger.trace("Item in: %s", item)
        if item == "EOF":
            logger.debug("In Queue Exhausted")
            # Re-put EOF into queue for other threads
            self.queues["in"].put(item)
        return item

    def get_batch(self):
        """ Get items from the queue in batches of
            self.batch_size

            First item in output tuple indicates whether the
            queue is exhausted.
            Second item is the batch

            Remember to put "EOF" to the out queue after processing
            the final batch """
        exhausted = False
        batch = list()
        for _ in range(self.batch_size):
            item = self.get_item()
            if item == "EOF":
                exhausted = True
                break
            batch.append(item)
        logger.trace("Returning batch size: %s", len(batch))
        return (exhausted, batch)

    # <<< MISC METHODS >>> #
    def get_vram_free(self):
        """ Return free and total VRAM on card with most VRAM free"""
        stats = GPUStats()
        vram = stats.get_card_most_free(supports_plaidml=self.supports_plaidml)
        logger.verbose("Using device %s with %sMB free of %sMB",
                       vram["device"],
                       int(vram["free"]),
                       int(vram["total"]))
        return int(vram["card_id"]), int(vram["free"]), int(vram["total"])
 </s> add                                (self.input_size, self.input_size))
        if channels_first:
            image = np.moveaxis(image, 2, 0) </s> remove 
    # <<< FINALIZE METHODS >>> #
    def finalize(self, output):
        """ This should be called as the final task of each plugin
            aligns faces and puts to the out queue """
        if output == "EOF":
            logger.trace("Item out: %s", output)
            self.queues["out"].put("EOF")
            return
        logger.trace("Item out: %s", {key: val
                                      for key, val in output.items()
                                      if key != "image"})
        self.queues["out"].put((output))

    # <<< MISC METHODS >>> #
    def get_vram_free(self):
        """ Return free and total VRAM on card with most VRAM free"""
        stats = GPUStats()
        vram = stats.get_card_most_free(supports_plaidml=self.supports_plaidml)
        logger.verbose("Using device %s with %sMB free of %sMB",
                       vram["device"],
                       int(vram["free"]),
                       int(vram["total"]))
        return int(vram["card_id"]), int(vram["free"]), int(vram["total"])

    def get_item(self):
        """ Yield one item from the queue """
        while True:
            item = self.queues["in"].get()
            if isinstance(item, dict):
                logger.trace("Item in: %s", {key: val
                                             for key, val in item.items()
                                             if key != "image"})
                # Pass Detector failures straight out and quit
                if item.get("exception", None):
                    self.queues["out"].put(item)
                    exit(1)
            else:
                logger.trace("Item in: %s", item)
            yield item
            if item == "EOF":
                break
 </s> add 
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
          for item in self.get_item():
              if item == "EOF":
                  self.finalize(item)
                  break
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue

            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {k: v[f_idx + 1:] if k == "detected_faces" else v
                                          for k, v in item.items()}
                        logger.trace("Rolled over %s faces of %s to next batch for '%s'",
                                     len(self._rollover["detected_faces"]),
                                     frame_faces, item["filename"])
                    break
        if batch:
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items()})
          else:
              cvt_image = image.copy()
          return cvt_image
  
      def process_landmarks(self, image, detected_faces):
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 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/align/_base.py