              logger.info("Waiting for Aligner... Time out in %s minutes", mins)
  
          logger.debug("Launched Aligner")
  
      def launch_detector(self):
          """ Launch the face detector """
          logger.debug("Launching Detector")
          kwargs = {"in_queue": self.queues["extract_detect_in"],
                    "out_queue": self.queues["extract_align_in"]}
          mp_func = PoolProcess if self.detector.parent_is_pool else SpawnProcess
 
</s> remove         kwargs = {"in_queue": self.queues["extract_detect_in"],
                  "out_queue": self.queues["extract_align_in"]}
        mp_func = PoolProcess if self.detector.parent_is_pool else SpawnProcess
        process = mp_func(self.detector.run, **kwargs)

        event = process.event if hasattr(process, "event") else None
        error = process.error if hasattr(process, "error") else None
        process.start()
        self.processes.append(process)

        if event is None:
            logger.debug("Launched Detector")
            return

        for mins in reversed(range(5)):
            for seconds in range(60):
                event.wait(seconds)
                if event.is_set():
                    break
                if error and error.is_set():
                    break
            if event.is_set():
                break
            if mins == 0 or (error and error.is_set()):
                raise ValueError("Error initializing Detector")
            logger.info("Waiting for Detector... Time out in %s minutes", mins)

 </s> add         kwargs = dict(in_queue=self._queues["extract_detect_in"],
                      out_queue=self._queues["extract_align_in"],
                      queue_size=self._queue_size)
        self._detector.initialize(**kwargs)
        self._detector.start() </s> remove         kwargs = {"in_queue": self.queues["extract_align_in"],
                  "out_queue": self.queues["extract_align_out"]}

        process = SpawnProcess(self.aligner.run, **kwargs)
        event = process.event
        error = process.error
        process.start()
        self.processes.append(process)

        # Wait for Aligner to take it's VRAM
        # The first ever load of the model for FAN has reportedly taken
        # up to 3-4 minutes, hence high timeout.
        # TODO investigate why this is and fix if possible
        for mins in reversed(range(5)):
            for seconds in range(60):
                event.wait(seconds)
                if event.is_set():
                    break
                if error.is_set():
                    break
            if event.is_set():
                break
            if mins == 0 or error.is_set():
                raise ValueError("Error initializing Aligner")
            logger.info("Waiting for Aligner... Time out in %s minutes", mins)

 </s> add         kwargs = dict(in_queue=self._queues["extract_align_in"],
                      out_queue=self._queues["extract_align_out"],
                      queue_size=self._queue_size)
        self._aligner.initialize(**kwargs)
        self._aligner.start() </s> remove     def launch_aligner(self):
 </s> add     def _launch_aligner(self): </s> remove     def launch(self):
        """ Launches the plugins
            This can be called multiple times depending on the phase/whether multiprocessing
            is enabled.

            If multiprocessing:
                launches both plugins, but aligner first so that it's VRAM can be allocated
                prior to giving the remaining to the detector
            If not multiprocessing:
                Launches the relevant plugin for the current phase """
        if self.is_parallel:
            logger.debug("Launching aligner and detector")
            self.launch_aligner()
            self.launch_detector()
        elif self.phase == "detect":
            logger.debug("Launching detector")
            self.launch_detector()
        else:
            logger.debug("Launching aligner")
            self.launch_aligner()
 </s> add     @staticmethod
    def _load_aligner(aligner, configfile, normalize_method):
        """ Set global arguments and load aligner plugin """
        aligner_name = aligner.replace("-", "_").lower()
        logger.debug("Loading Aligner: '%s'", aligner_name)
        aligner = PluginLoader.get_aligner(aligner_name)(configfile=configfile,
                                                         normalize_method=normalize_method)
        return aligner </s> remove     def detected_faces(self):
        """ Detect faces from in an image """
        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:
                faces = out_queue.get(True, 1)
                if faces == "EOF":
                    break
                if isinstance(faces, dict) and faces.get("exception"):
                    pid = faces["exception"][0]
                    t_back = faces["exception"][1].getvalue()
                    err = "Error in child process {}. {}".format(pid, t_back)
                    raise Exception(err)
            except QueueEmpty:
                continue

            yield faces
        for process in self.processes:
            logger.trace("Joining process: %s", process)
            process.join()
            del process
        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"
 </s> add     def _set_extractor_batchsize(self, vram_required, vram_free):
        """ Sets the batchsize of the used plugins based on their vram and
            vram_per_batch_requirements """
        batch_required = ((self._aligner.vram_per_batch * self._aligner.batchsize) +
                          (self._detector.vram_per_batch * self._detector.batchsize))
        plugin_required = vram_required + batch_required
        if plugin_required <= vram_free:
            logger.verbose("Plugin requirements within threshold: (plugin_required: %sMB, "
                           "vram_free: %sMB)", plugin_required, vram_free)
            return
        # Hacky split across 2 plugins
        available_for_batching = (vram_free - vram_required) // 2
        self._aligner.batchsize = max(1, available_for_batching // self._aligner.vram_per_batch)
        self._detector.batchsize = max(1, available_for_batching // self._detector.vram_per_batch)
        logger.verbose("Reset batchsizes: (aligner: %s, detector: %s)",
                       self._aligner.batchsize, self._detector.batchsize)

    def _join_threads(self):
        """ Join threads for current pass """
        for plugin in self._active_plugins:
            plugin.join()

    def _check_and_raise_error(self):
        """ Check all threads for errors and raise if one occurs """
        for plugin in self._active_plugins:
            if plugin.check_and_raise_error():
                return True
        return False
    def _launch_detector(self):
              logger.info("Waiting for Aligner... Time out in %s minutes", mins)
  
          logger.debug("Launched Aligner")
  
    def _launch_detector(self):
          """ Launch the face detector """
          logger.debug("Launching Detector")
          kwargs = {"in_queue": self.queues["extract_detect_in"],
                    "out_queue": self.queues["extract_align_in"]}
          mp_func = PoolProcess if self.detector.parent_is_pool else SpawnProcess
keep keep keep keep replace keep keep keep keep keep
 De-Multiprocess Extract (#871)

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

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

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

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

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

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

* Standardize all extract plugins to new threaded codebase

* Documentation. Start implementing Numpy style docstrings for Sphinx Documentation

* Remove s3fd_amd. Change convert OTF to expect DetectedFace object

* faces_detect - clean up and documentation

* Remove PoolProcess

* Migrate manual tool to new extract workflow

* Remove AMD specific extractor code from cli and plugins

* Sort tool to new extract workflow

* Remove multiprocessing from project

* Remove multiprocessing queues from QueueManager

* Remove multiprocessing support from logger

* Move face_filter to new extraction pipeline

* Alignments landmarksXY > landmarks_xy and legacy handling

* Intercept get_backend for sphinx doc build

# Add Sphinx documentation 
plugins/extract/pipeline.py