          self.output = self.get_outputs()
          self.session = self.set_session(target_size, vram_ratio, card_id)
          logger.debug("Initialized: %s", self.__class__.__name__)
  
      def load_graph(self):
          """ Load the tensorflow Model and weights """
          # pylint: disable=not-context-manager
          logger.verbose("Initializing S3FD Network model...")
          with self.tf.gfile.GFile(self.model_path, "rb") as gfile:
              graph_def = self.tf.GraphDef()
              graph_def.ParseFromString(gfile.read())
          fa_graph = self.tf.Graph()
          with fa_graph.as_default():
              self.tf.import_graph_def(graph_def, name="s3fd")
          return fa_graph
  
      def get_outputs(self):
          """ Return the output tensors """
          tensor_names = ["concat_31", "transpose_72", "transpose_75", "transpose_78",
                          "transpose_81", "transpose_84", "transpose_87", "transpose_90",
                          "transpose_93", "transpose_96", "transpose_99", "transpose_102"]
          logger.debug("tensor_names: %s", tensor_names)
          tensors = [self.graph.get_tensor_by_name("s3fd/{}:0".format(t_name))
                     for t_name in tensor_names]
          logger.debug("tensors: %s", tensors)
          return tensors
  
      def set_session(self, target_size, vram_ratio, card_id):
          """ Set the TF Session and initialize """
          # pylint: disable=not-context-manager, no-member
          placeholder = np.zeros((1, 3, target_size[0], target_size[1]))
          config = self.tf.ConfigProto()
          if card_id != -1:
              config.gpu_options.visible_device_list = str(card_id)
          if vram_ratio != 1.0:
              config.gpu_options.per_process_gpu_memory_fraction = vram_ratio
  
          with self.graph.as_default():
              session = self.tf.Session(config=config)
              self.is_gpu = any("gpu" in str(device).lower() for device in session.list_devices())
              session.run(self.output, feed_dict={self.input: placeholder})
          return session
  
      def detect_face(self, feed_item):
          """ Detect faces """
          feed_item = feed_item - np.array([104.0, 117.0, 123.0])
          feed_item = feed_item.transpose(2, 0, 1)
          feed_item = feed_item.reshape((1,) + feed_item.shape).astype('float32')
          bboxlist = self.session.run(self.output, feed_dict={self.input: feed_item})
          bboxlist = self.post_process(bboxlist)
  
          keep = self.nms(bboxlist, 0.3)
          bboxlist = bboxlist[keep, :]
          bboxlist = [x for x in bboxlist if x[-1] >= self.confidence]
  
          return np.array(bboxlist)
  
      def post_process(self, bboxlist):
          """ Perform post processing on output """
 
</s> remove         return np.array(bboxlist)
 </s> add     def finalize_predictions(self, bboxlists):
        """ Detect faces """
        ret = list()
        for i in range(bboxlists[0].shape[0]):
            bboxlist = [x[i:i+1, ...] for x in bboxlists]
            bboxlist = self.post_process(bboxlist)
            keep = self.nms(bboxlist, 0.3)
            bboxlist = bboxlist[keep, :]
            bboxlist = [x for x in bboxlist if x[-1] >= self.confidence]
            ret.append(np.array(bboxlist))
        return ret </s> remove         var_b = prediction.reshape((prediction.shape[0],
                                    prediction.shape[1] * prediction.shape[2]))
        var_c = var_b.argmax(1).reshape((prediction.shape[0],
                                         1)).repeat(2,
                                                    axis=1).astype(np.float)
        var_c[:, 0] %= prediction.shape[2]
        var_c[:, 1] = np.apply_along_axis(
            lambda x: np.floor(x / prediction.shape[2]),
            0,
            var_c[:, 1])

        for i in range(prediction.shape[0]):
            pt_x, pt_y = int(var_c[i, 0]), int(var_c[i, 1])
            if pt_x > 0 and pt_x < 63 and pt_y > 0 and pt_y < 63:
                diff = np.array([prediction[i, pt_y, pt_x+1]
                                 - prediction[i, pt_y, pt_x-1],
                                 prediction[i, pt_y+1, pt_x]
                                 - prediction[i, pt_y-1, pt_x]])

                var_c[i] += np.sign(diff)*0.25

        var_c += 0.5
        retval = [self.transform(var_c[i], center, scale, prediction.shape[2])
                  for i in range(prediction.shape[0])]
        logger.trace("Obtained points from prediction: %s", retval)

        return retval


class FAN():
    """The FAN Model.
    Converted from pyTorch via ONNX from:
    https://github.com/1adrianb/face-alignment """

    def __init__(self, model_path, ratio=1.0):
        # Must import tensorflow inside the spawned process
        # for Windows machines
        import tensorflow as tf
        self.tf = tf  # pylint: disable=invalid-name

        self.model_path = model_path
        self.graph = self.load_graph()
        self.input = self.graph.get_tensor_by_name("fa/input_1:0")
        self.output = self.graph.get_tensor_by_name("fa/transpose_647:0")
        self.session = self.set_session(ratio)

    def load_graph(self):
        """ Load the tensorflow Model and weights """
        # pylint: disable=not-context-manager
        logger.verbose("Initializing Face Alignment Network model...")

        with self.tf.gfile.GFile(self.model_path, "rb") as gfile:
            graph_def = self.tf.GraphDef()
            graph_def.ParseFromString(gfile.read())
        fa_graph = self.tf.Graph()
        with fa_graph.as_default():
            self.tf.import_graph_def(graph_def, name="fa")
        return fa_graph

    def set_session(self, vram_ratio):
        """ Set the TF Session and initialize """
        # pylint: disable=not-context-manager, no-member
        placeholder = np.zeros((1, 3, 256, 256))
        with self.graph.as_default():
            config = self.tf.ConfigProto()
            config.gpu_options.per_process_gpu_memory_fraction = vram_ratio
            session = self.tf.Session(config=config)
            with session.as_default():
                if any("gpu" in str(device).lower() for device in session.list_devices()):
                    logger.debug("Using GPU")
                else:
                    logger.warning("Using CPU")
                session.run(self.output, feed_dict={self.input: placeholder})
        return session

    def predict(self, feed_item):
        """ Predict landmarks in session """
        return self.session.run(self.output,
                                feed_dict={self.input: feed_item})
 </s> add         landmarks = []
        for prediction, center, scale in zip(batch["prediction"], *batch["center_scale"]):
            var_b = prediction.reshape((prediction.shape[0],
                                        prediction.shape[1] * prediction.shape[2]))
            var_c = var_b.argmax(1).reshape((prediction.shape[0],
                                             1)).repeat(2,
                                                        axis=1).astype(np.float)
            var_c[:, 0] %= prediction.shape[2]
            var_c[:, 1] = np.apply_along_axis(
                lambda x: np.floor(x / prediction.shape[2]),
                0,
                var_c[:, 1])

            for i in range(prediction.shape[0]):
                pt_x, pt_y = int(var_c[i, 0]), int(var_c[i, 1])
                if 63 > pt_x > 0 and 63 > pt_y > 0:
                    diff = np.array([prediction[i, pt_y, pt_x+1]
                                     - prediction[i, pt_y, pt_x-1],
                                     prediction[i, pt_y+1, pt_x]
                                     - prediction[i, pt_y-1, pt_x]])

                    var_c[i] += np.sign(diff)*0.25

            var_c += 0.5
            landmarks = [self.transform(var_c[i], center, scale, prediction.shape[2])
                         for i in range(prediction.shape[0])]
            batch.setdefault("landmarks", []).append(landmarks)
        logger.trace("Obtained points from prediction: %s", batch["landmarks"])


class TorchBatchNorm2D(keras.engine.base_layer.Layer):
    # pylint:disable=too-many-instance-attributes
    """" Required for FAN_keras model """
    def __init__(self, axis=-1, momentum=0.99, epsilon=1e-3, **kwargs):
        super(TorchBatchNorm2D, self).__init__(**kwargs)
        self.supports_masking = True
        self.axis = axis
        self.momentum = momentum
        self.epsilon = epsilon
        self._epsilon_const = K.constant(self.epsilon, dtype='float32')

        self.built = False
        self.gamma = None
        self.beta = None
        self.moving_mean = None
        self.moving_variance = None

    def build(self, input_shape):
        dim = input_shape[self.axis]
        if dim is None:
            raise ValueError("Axis {} of input tensor should have a "
                             "defined dimension but the layer received "
                             "an input with  shape {}."
                             .format(str(self.axis), str(input_shape)))
        shape = (dim,)
        self.gamma = self.add_weight(shape=shape,
                                     name='gamma',
                                     initializer='ones',
                                     regularizer=None,
                                     constraint=None)
        self.beta = self.add_weight(shape=shape,
                                    name='beta',
                                    initializer='zeros',
                                    regularizer=None,
                                    constraint=None)
        self.moving_mean = self.add_weight(shape=shape,
                                           name='moving_mean',
                                           initializer='zeros',
                                           trainable=False)
        self.moving_variance = self.add_weight(shape=shape,
                                               name='moving_variance',
                                               initializer='ones',
                                               trainable=False)
        self.built = True

    def call(self, inputs, **kwargs):
        input_shape = K.int_shape(inputs)

        broadcast_shape = [1] * len(input_shape)
        broadcast_shape[self.axis] = input_shape[self.axis]

        broadcast_moving_mean = K.reshape(self.moving_mean, broadcast_shape)
        broadcast_moving_variance = K.reshape(self.moving_variance,
                                              broadcast_shape)
        broadcast_gamma = K.reshape(self.gamma, broadcast_shape)
        broadcast_beta = K.reshape(self.beta, broadcast_shape)
        invstd = (
            K.ones(shape=broadcast_shape, dtype='float32')
            / K.sqrt(broadcast_moving_variance + self._epsilon_const)
        )

        return((inputs - broadcast_moving_mean)
               * invstd
               * broadcast_gamma
               + broadcast_beta)

    def get_config(self):
        config = {'axis': self.axis,
                  'momentum': self.momentum,
                  'epsilon': self.epsilon}
        base_config = super(TorchBatchNorm2D, self).get_config()
        return dict(list(base_config.items()) + list(config.items())) </s> remove         self.graph = self.load_graph()
        self.input = self.graph.get_tensor_by_name("s3fd/input_1:0")
        self.output = self.get_outputs()
        self.session = self.set_session(target_size, vram_ratio, card_id)
 </s> add  </s> remove             if not self.model.is_gpu:
                alloc = 2048
                logger.warning("Using CPU")
            else:
                logger.debug("Using GPU")
                alloc = vram_free
            logger.debug("Allocated for Tensorflow: %sMB", alloc)

            if self.min_vram < alloc < self.vram:
                self.batch_size = 1
                logger.warning("You are running s3fd with %sMB VRAM. The model is optimized for "
                               "%sMB VRAM. Detection should still run but you may get "
                               "warnings/errors", int(alloc), self.vram)
            else:
                self.batch_size = int(alloc / self.vram)
            if self.batch_size < 1:
                raise ValueError("Insufficient VRAM available to continue "
                                 "({}MB)".format(int(alloc)))

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

            self.init.set()
            logger.info("Initialized S3FD 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"], is_square=True)
            for angle in self.rotation:
                current_image, rotmat = self.rotate_image(detect_image, angle)
                faces = self.model.detect_face(current_image)
                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, rotmat, scale)
            item["detected_faces"] = detected_faces
            self.finalize(item)

        logger.debug("Thread Completed Detect")

    def process_output(self, faces, rotation_matrix, scale):
        """ Compile found faces for output """
        logger.trace("Processing Output: (faces: %s, rotation_matrix: %s)", faces, rotation_matrix)
        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


class S3fd():
    """ Tensorflow Network """
    def __init__(self, model_path, target_size, vram_ratio, card_id, confidence):
        logger.debug("Initializing: %s: (model_path: '%s', target_size: %s, vram_ratio: %s, "
                     "card_id: %s)",
                     self.__class__.__name__, model_path, target_size, vram_ratio, card_id)
        # Must import tensorflow inside the spawned process for Windows machines
        import tensorflow as tf
        self.is_gpu = False
        self.tf = tf  # pylint: disable=invalid-name
        self.model_path = model_path
 </s> add class O2K_Sum(O2K_ReduceLayer):
    def call(self, x, *args):
        return K.sum(x, self._axes, self._keepdims)


class O2K_Sqrt(keras.engine.Layer):
    def call(self, x, *args):
        return K.sqrt(x)


class O2K_Pow(keras.engine.Layer):
    def call(self, x, *args):
        return K.pow(*x)


class O2K_ConstantLayer(keras.engine.Layer):
    def __init__(self, constant_obj, dtype, **kwargs):
        self._dtype = np.dtype(dtype).name
        self._constant = np.array(constant_obj, dtype=self._dtype)
        super(O2K_ConstantLayer, self).__init__(**kwargs)

    def call(self, *args):
        # pylint:disable=arguments-differ
        data = K.constant(self._constant, dtype=self._dtype)
        return data

    def compute_output_shape(self, input_shape):
        return self._constant.shape

    def get_config(self):
        config = super(O2K_ConstantLayer, self).get_config()
        config.update({
            'constant_obj': self._constant,
            'dtype': self._dtype
        })
        return config


class O2K_Div(O2K_ElementwiseLayer):
    # pylint:disable=arguments-differ
    def call(self, x, *args):
        return x[0] / x[1]


class S3fd(KSession):
    """ Keras Network """
    def __init__(self, model_path, model_kwargs, confidence):
        logger.debug("Initializing: %s: (model_path: '%s')",
                     self.__class__.__name__, model_path)
        super().__init__("S3FD", model_path, model_kwargs)
        self.load_model() </s> remove         """ Perform post processing on output """
 </s> add         """ Perform post processing on output
            TODO: do this on the batch.
        """
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
          self.output = self.get_outputs()
          self.session = self.set_session(target_size, vram_ratio, card_id)
          logger.debug("Initialized: %s", self.__class__.__name__)
  
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
    @staticmethod
    def prepare_batch(batch):
        """ Prepare a batch for prediction """
        batch = batch - np.array([104.0, 117.0, 123.0])
        batch = batch.transpose(0, 3, 1, 2)
        return batch
  
          return np.array(bboxlist)
  
      def post_process(self, bboxlist):
          """ Perform post processing on output """
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 replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace keep keep keep keep keep
 De-Multiprocess Extract (#871)

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

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

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

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

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

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

* Standardize all extract plugins to new threaded codebase

* Documentation. Start implementing Numpy style docstrings for Sphinx Documentation

* Remove s3fd_amd. Change convert OTF to expect DetectedFace object

* faces_detect - clean up and documentation

* Remove PoolProcess

* Migrate manual tool to new extract workflow

* Remove AMD specific extractor code from cli and plugins

* Sort tool to new extract workflow

* Remove multiprocessing from project

* Remove multiprocessing queues from QueueManager

* Remove multiprocessing support from logger

* Move face_filter to new extraction pipeline

* Alignments landmarksXY > landmarks_xy and legacy handling

* Intercept get_backend for sphinx doc build

# Add Sphinx documentation 
plugins/extract/detect/s3fd.py