              logger.debug("Parallel processing disabled by cli.")
              return False
  
          gpu_stats = GPUStats()
          if gpu_stats.is_plaidml and (not self.detector.supports_plaidml or
                                       not self.aligner.supports_plaidml):
              logger.debug("At least one of aligner or detector does not support plaidML. "
                           "Enabling parallel processing.")
              return True
  
          if not gpu_stats.is_plaidml and (
                  (self.detector.supports_plaidml and aligner_vram != 0) or
                  (self.aligner.supports_plaidml and detector_vram != 0)):
              logger.warning("Keras + non-Keras aligner/detector combination does not support "
                             "parallel processing. Switching to serial.")
              return False
  
          if self.detector.supports_plaidml and self.aligner.supports_plaidml:
              logger.debug("Both aligner and detector support plaidML. Disabling parallel "
                           "processing.")
              return False
  
          if gpu_stats.device_count == 0:
              logger.debug("No GPU detected. Enabling parallel processing.")
              return True
  
          required_vram = detector_vram + aligner_vram + 320  # 320MB buffer
 
</s> remove         required_vram = detector_vram + aligner_vram + 320  # 320MB buffer
 </s> add         if get_backend() == "amd":
            logger.debug("Parallel processing discabled by amd")
            return False

        vram_required = self._detector.vram + self._aligner.vram + self._vram_buffer </s> remove         if detector_vram == 0 or aligner_vram == 0:
 </s> add     def _set_parallel_processing(self, multiprocess):
        """ Set whether to run detect and align together or separately """
        if self._detector.vram == 0 or self._aligner.vram == 0: </s> remove         free_vram = int(stats["free"])
 </s> add         vram_free = int(stats["free"]) </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 </s> remove     @staticmethod
    def load_aligner(aligner, loglevel, 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)(loglevel=loglevel,
                                                         configfile=configfile,
                                                         normalize_method=normalize_method)
        return aligner
 </s> add         This should be set prior to :func:`launch` if the batchsize is to be manually overriden
              logger.debug("Parallel processing disabled by cli.")
              return False
  
          gpu_stats = GPUStats()


















          if gpu_stats.device_count == 0:
              logger.debug("No GPU detected. Enabling parallel processing.")
              return True
  
          required_vram = detector_vram + aligner_vram + 320  # 320MB buffer
keep keep keep keep 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/pipeline.py