              logger.info("Waiting for Detector... Time out in %s minutes", mins)
  
          logger.debug("Launched Detector")
  
      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> 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     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         queue_manager.get_queue("in").put({"image": self.media["image"],
                                           "filename": self.media["frame_id"],
                                           "face": self.media["bounding_box"]})
        landmarks = queue_manager.get_queue("out").get()

        if isinstance(landmarks, dict) and landmarks.get("exception"):
            cv2.destroyAllWindows()  # pylint: disable=no-member
            pid = landmarks["exception"][0]
            t_back = landmarks["exception"][1].getvalue()
            err = "Error in child process {}. {}".format(pid, t_back)
            raise Exception(err)
        if landmarks == "EOF":
            exit(0)

        alignment = self.extracted_to_alignment((landmarks["detected_faces"][0],
                                                 landmarks["landmarks"][0]))
 </s> add         self.queues["in"].put({"image": self.media["image"],
                               "filename": self.media["frame_id"],
                               "manual_face": self.media["bounding_box"]})
        detected_face = next(self.extractor.detected_faces())["detected_faces"][0]
        alignment = detected_face.to_alignment()
 </s> remove         out_queue = queue_manager.get_queue("out")
        kwargs = {"in_queue": queue_manager.get_queue("in"),
                  "out_queue": out_queue}

        for plugin in ("fan", "cv2_dnn"):
            aligner = PluginLoader.get_aligner(plugin)(loglevel=self.args.loglevel)
            process = SpawnProcess(aligner.run, **kwargs)
            event = process.event
            process.start()
            # Wait for Aligner to take init
            # The first ever load of the model for FAN has reportedly taken
            # up to 3-4 minutes, hence high timeout.
            event.wait(300)

            if not event.is_set():
                if plugin == "fan":
                    process.join()
                    logger.error("Error initializing FAN. Trying CV2-DNN")
                    continue
                else:
                    raise ValueError("Error inititalizing Aligner")
            if plugin == "cv2_dnn":
                return

            try:
                err = None
                err = out_queue.get(True, 1)
            except QueueEmpty:
                pass
            if not err:
                break
            process.join()
            logger.error("Error initializing FAN. Trying CV2-DNN")
 </s> add         kwargs = dict(in_queue=queue_manager.get_queue("in"),
                      out_queue=queue_manager.get_queue("out"),
                      queue_size=8)
        aligner = PluginLoader.get_aligner("fan")(normalize_method="hist")
        aligner.batchsize = 1
        aligner.initialize(**kwargs)
        aligner.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()
    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
              logger.info("Waiting for Detector... Time out in %s minutes", mins)
  
          logger.debug("Launched Detector")
  
    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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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
keep keep keep keep replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace
 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