          """ Run extractor to get faces """
          exception = False
          for _ in range(extractor.passes):
              self.queue_images(extractor)
              if exception:
                  break
              extractor.launch()
              for faces in extractor.detected_faces():
                  exception = faces.get("exception", False)
                  if exception:
                      break
 
</s> remove         exception = False
 </s> add  </s> remove                 exception = faces.get("exception", False)
                if exception:
                    break
 </s> add  </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     # <<< 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 initialize(self, *args, **kwargs):
        """ Create the mtcnn detector """
        try:
            super().initialize(*args, **kwargs)
            logger.info("Initializing MTCNN Detector...")
            is_gpu = False

            # Must import tensorflow inside the spawned process
            # for Windows machines
            import_tensorflow()
            _, vram_free, _ = self.get_vram_free()
            mtcnn_graph = tf.Graph()

            # Windows machines sometimes misreport available vram, and overuse
            # causing OOM. Allow growth fixes that
            config = tf.ConfigProto()
            config.gpu_options.allow_growth = True  # pylint: disable=no-member

            with mtcnn_graph.as_default():  # pylint: disable=not-context-manager
                sess = tf.Session(config=config)
                with sess.as_default():  # pylint: disable=not-context-manager
                    pnet, rnet, onet = create_mtcnn(sess, self.model_path)

                if any("gpu" in str(device).lower()
                       for device in sess.list_devices()):
                    logger.debug("Using GPU")
                    is_gpu = True
            mtcnn_graph.finalize()

            if not is_gpu:
                alloc = 2048
                logger.warning("Using CPU")
            else:
                alloc = vram_free
            logger.debug("Allocated for Tensorflow: %sMB", alloc)

            self.batch_size = int(alloc / self.vram)

            if self.batch_size < 1:
                self.error.set()
                raise ValueError("Insufficient VRAM available to continue "
                                 "({}MB)".format(int(alloc)))

            logger.verbose("Processing in %s threads", self.batch_size)

            self.kwargs["pnet"] = pnet
            self.kwargs["rnet"] = rnet
            self.kwargs["onet"] = onet

            self.init.set()
            logger.info("Initialized MTCNN Detector.")
        except Exception as err:
            self.error.set()
            raise err

    def detect_faces(self, *args, **kwargs):
        """ Detect faces in Multiple Threads """
        super().detect_faces(*args, **kwargs)
        workers = MultiThread(target=self.detect_thread, thread_count=self.batch_size)
        workers.start()
        workers.join()
        sentinel = self.queues["in"].get()
        self.queues["out"].put(sentinel)
        logger.debug("Detecting Faces complete")

    def detect_thread(self):
        """ Detect faces in rgb image """
        logger.debug("Launching Detect")
        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"], to_rgb=True)

            for angle in self.rotation:
                current_image, rotmat = self.rotate_image(detect_image, angle)
                faces, points = detect_face(current_image, **self.kwargs)
                if angle != 0 and faces.any():
                    logger.verbose("found face(s) by rotating image %s degrees", angle)
                if faces.any():
                    break

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

        logger.debug("Thread Completed Detect")

    def process_output(self, faces, points, rotation_matrix, scale):
        """ Compile found faces for output """
        logger.trace("Processing Output: (faces: %s, points: %s, rotation_matrix: %s)",
                     faces, points, rotation_matrix)
        faces = self.recalculate_bounding_box(faces, points)
        faces = [self.to_bounding_box_dict(face[0], face[1], face[2], face[3]) for face in faces]
        if isinstance(rotation_matrix, np.ndarray):
            faces = [self.rotate_rect(face, rotation_matrix)
                     for face in faces]
        detected = [self.to_bounding_box_dict(face["left"] / scale, face["top"] / scale,
                                              face["right"] / scale, face["bottom"] / scale)
                    for face in faces]
        logger.trace("Processed Output: %s", detected)
        return detected
 </s> add     def init_model(self):
        """ Initialize S3FD Model"""
        self.model = MTCNN(self.model_path, **self.kwargs)
          """ Run extractor to get faces """
          exception = False
          for _ in range(extractor.passes):
              self.queue_images(extractor)


              extractor.launch()
              for faces in extractor.detected_faces():
                  exception = faces.get("exception", False)
                  if exception:
                      break
keep keep keep keep 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 
lib/face_filter.py