              self.queues["out"].put(exception)
              exit(1)
  
      # <<< FINALIZE METHODS>>> #
      def finalize(self, output):
          """ This should be called as the final task of each plugin
              Performs fianl processing and puts to the out queue """
          if isinstance(output, dict):
              logger.trace("Item out: %s", {key: val
                                            for key, val in output.items()
                                            if key != "image"})
              # Prevent zero size faces
              iheight, iwidth = output["image"].shape[:2]
              output["detected_faces"] = [
                  f for f in output.get("detected_faces", list())
                  if f["right"] > 0 and f["left"] < iwidth
                  and f["bottom"] > 0 and f["top"] < iheight
              ]
              if self.min_size > 0 and output.get("detected_faces", None):
                  output["detected_faces"] = self.filter_small_faces(output["detected_faces"])
          else:
              logger.trace("Item out: %s", output)
          self.queues["out"].put(output)
  
      def filter_small_faces(self, detected_faces):
          """ Filter out any faces smaller than the min size threshold """
          retval = list()
          for face in detected_faces:
 
</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  </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 </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     # <<< OVERRIDE METHODS >>> #
    def initialize(self, *args, **kwargs):
        """ Inititalize the detector
            Tasks to be run before any detection is performed.
            Override for specific detector """
        logger.debug("initialize %s (PID: %s, args: %s, kwargs: %s)",
                     self.__class__.__name__, os.getpid(), args, kwargs)
        self.init = kwargs.get("event", False)
        self.error = kwargs.get("error", False)
        self.queues["in"] = kwargs["in_queue"]
        self.queues["out"] = kwargs["out_queue"]

    def detect_faces(self, *args, **kwargs):
        """ Detect faces in rgb image
            Override for specific detector
            Must return a list of bounding box dicts (See module docstring)"""
        try:
            if not self.init:
                self.initialize(*args, **kwargs)
        except ValueError as err:
            logger.error(err)
            exit(1)
        logger.debug("Detecting Faces (args: %s, kwargs: %s)", args, kwargs)

    # <<< GET MODEL >>> #
    @staticmethod
    def get_model(git_model_id, model_filename):
        """ Check if model is available, if not, download and unzip it """
        if model_filename is None:
            logger.debug("No model_filename specified. Returning None")
            return None
        if git_model_id is None:
            logger.debug("No git_model_id specified. Returning None")
            return None
        cache_path = os.path.join(os.path.dirname(__file__), ".cache")
        model = GetModel(model_filename, cache_path, git_model_id)
        return model.model_path

    # <<< DETECTION WRAPPER >>> #
    def run(self, *args, **kwargs):
        """ Parent detect process.
            This should always be called as the entry point so exceptions
            are passed back to parent.
            Do not override """
        try:
            logger.debug("Executing detector run function")
            self.detect_faces(*args, **kwargs)
        except Exception as err:  # pylint: disable=broad-except
            logger.error("Caught exception in child process: %s: %s", os.getpid(), str(err))
            # Display traceback if in initialization stage
            if not self.init.is_set():
                logger.exception("Traceback:")
            tb_buffer = StringIO()
            traceback.print_exc(file=tb_buffer)
            logger.trace(tb_buffer.getvalue())
            exception = {"exception": (os.getpid(), tb_buffer)}
            self.queues["out"].put(exception)
            exit(1)
 </s> add     # <<< QUEUE METHODS >>> #
    def get_batch(self, queue):
        """ Get items for inputting to the detector plugin in batches

        Items are returned from the ``queue`` in batches of
        :attr:`~plugins.extract._base.Extractor.batchsize`

        Remember to put ``'EOF'`` to the out queue after processing
        the final batch

        Outputs items in the following format. All lists are of length
        :attr:`~plugins.extract._base.Extractor.batchsize`:

        >>> {'filename': [<filenames of source frames>],
        >>>  'image': [<source images>],
        >>>  'scaled_image': <np.array of images standardized for prediction>,
        >>>  'scale': [<scaling factors for each image>],
        >>>  'pad': [<padding for each image>],
        >>>  'detected_faces': [[<lib.faces_detect.DetectedFace objects]]}

        Parameters
        ----------
        queue : queue.Queue()
            The ``queue`` that the batch will be fed from. This will be a queue that loads
            images.

        Returns
        -------
        exhausted, bool
            ``True`` if queue is exhausted, ``False`` if not.
        batch, dict
            A dictionary of lists of :attr:`~plugins.extract._base.Extractor.batchsize`.
        """
        exhausted = False
        batch = dict()
        for _ in range(self.batchsize):
            item = self._get_item(queue)
            if item == "EOF":
                exhausted = True
                break
            for key, val in item.items():
                batch.setdefault(key, []).append(val)
            scaled_image, scale, pad = self._compile_detection_image(item["image"])
            batch.setdefault("scaled_image", []).append(scaled_image)
            batch.setdefault("scale", []).append(scale)
            batch.setdefault("pad", []).append(pad)
        if batch:
            batch["scaled_image"] = np.array(batch["scaled_image"], dtype="float32")
            logger.trace("Returning batch: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                                 for k, v in batch.items() if k != "image"})
        else:
            logger.trace(item)
        return exhausted, batch </s> remove     def set_parallel_processing(self, multiprocess):
        """ Set whether to run detect and align together or separately """
        detector_vram = self.detector.vram
        aligner_vram = self.aligner.vram
 </s> add         Parameters
        ----------
        plugin_type: {'aligner', 'detector'}
            The plugin_type to be overriden
        batchsize: int
            The batchsize to use for this plugin type
        """
        logger.debug("Overriding batchsize for plugin_type: %s to: %s", plugin_type, batchsize)
        plugin = getattr(self, "_{}".format(plugin_type))
        plugin.batchsize = batchsize

    def launch(self):
        """ Launches the plugin(s)

        This launches the plugins held in the pipeline, and should be called at the beginning
        of each :attr:`phase`. To ensure VRAM is conserved, It will only launch the plugin(s)
        required for the currently running phase

        Example
        -------
        >>> for phase in extractor.passes:
        >>>     extractor.launch():
        >>>         <do processing>
        """

        if self._is_parallel:
            self._launch_aligner()
            self._launch_detector()
        elif self.phase == "detect":
            self._launch_detector()
        else:
            self._launch_aligner()

    def detected_faces(self):
        """ Generator that returns results, frame by frame from the extraction pipeline

        This is the exit point for the extraction pipeline and is used to obtain the output
        of any pipeline :attr:`phase`

        Yields
        ------
        faces: dict
            regardless of phase, the returned dictinary will contain, exclusively, ``filename``:
            the filename of the source image, ``image``: the ``numpy.array`` of the source image
            in BGR color format, ``detected_faces``: a list of
            :class:`~lib.faces_detect.Detected_Face` objects.

        Example
        -------
        >>> for face in extractor.detected_faces():
        >>>     filename = face["filename"]
        >>>     image = face["image"]
        >>>     detected_faces = face["detected_faces"]
        """
        logger.debug("Running Detection. Phase: '%s'", self.phase)
        # If not multiprocessing, intercept the align in queue for
        # detection phase
        out_queue = self._output_queue
        while True:
            try:
                if self._check_and_raise_error():
                    break
                faces = out_queue.get(True, 1)
                if faces == "EOF":
                    break
            except QueueEmpty:
                continue

            yield faces
        self._join_threads()
        if self.final_pass:
            # Cleanup queues
            for q_name in self._queues.keys():
                queue_manager.del_queue(q_name)
            logger.debug("Detection Complete")
        else:
            logger.debug("Switching to align phase")
            self.phase = "align"

    # <<< INTERNAL METHODS >>> #
    @property
    def _output_queue(self):
        """ Return the correct output queue depending on the current phase """
        qname = "extract_align_out" if self.final_pass else "extract_align_in"
        retval = self._queues[qname]
        logger.trace("%s: %s", qname, retval)
        return retval

    @property
    def _active_plugins(self):
        """ Return the plugins that are currently active based on pass """
        if self.passes == 1:
            retval = [self._detector, self._aligner]
        elif self.passes == 2 and not self.final_pass:
            retval = [self._detector]
        else:
            retval = [self._aligner]
        logger.trace("Active plugins: %s", retval)
        return retval

    def _add_queues(self):
        """ Add the required processing queues to Queue Manager """
        queues = dict()
        for task in ("extract_detect_in", "extract_align_in", "extract_align_out"):
            # Limit queue size to avoid stacking ram
            self._queue_size = 32
            if task == "extract_detect_in" or (not self._is_parallel
                                               and task == "extract_align_in"):
                self._queue_size = 64
            queue_manager.add_queue(task, maxsize=self._queue_size)
            queues[task] = queue_manager.get_queue(task)
        logger.debug("Queues: %s", queues)
        return queues
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
              self.queues["out"].put(exception)
              exit(1)
  
      # <<< FINALIZE METHODS>>> #
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
    def finalize(self, batch):
        """ Finalize the output from Detector

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

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        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 ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_rect(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        self._remove_invalid_keys(batch, ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace("final output: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                              for k, v in item.items()})
            yield item
  
      def filter_small_faces(self, detected_faces):
          """ Filter out any faces smaller than the min size threshold """
          retval = list()
          for face in 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 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