          retval = [(int(pt[0]), int(pt[1])) for pt in pts_img]
          logger.trace("Predicted Landmarks: %s", retval)
          return retval
  
      def get_pts_from_predict(self, prediction, center, scale):
          """ Get points from predictor """
          logger.trace("Obtain points from prediction")
          var_b = prediction.reshape((prediction.shape[0],
                                      prediction.shape[1] * prediction.shape[2]))
          var_c = var_b.argmax(1).reshape((prediction.shape[0],
 
</s> remove         image = np.expand_dims(
            feed_dict["image"].transpose((2, 0, 1)).astype(np.float32) / 255.0, 0)
        prediction = self.model.predict(image)[-1]
        pts_img = self.get_pts_from_predict(prediction, feed_dict["center"], feed_dict["scale"])
        retval = [(int(pt[0]), int(pt[1])) for pt in pts_img]
        logger.trace("Predicted Landmarks: %s", retval)
        return retval
 </s> add         batch["prediction"] = self.model.predict(batch["feed"])[-1]
        logger.trace([pred.shape for pred in batch["prediction"]])
        return batch

    def process_output(self, batch):
        """ Process the output from the model """
        self.get_pts_from_predict(batch)
        return batch </s> remove         logger.trace("Obtain points from prediction")
        points = np.array(prediction).flatten()
        points = np.reshape(points, (-1, 2))
        points *= (roi[2] - roi[0])
        points[:, 0] += roi[0]
        points[:, 1] += roi[1]
        retval = np.rint(points).astype("uint").tolist()
        logger.trace("Predicted Landmarks: %s", retval)
        return retval
 </s> add         for prediction, roi in zip(batch["prediction"], batch["roi"]):
            points = np.array(prediction).flatten()
            points = np.reshape(points, (-1, 2))
            points *= (roi[2] - roi[0])
            points[:, 0] += roi[0]
            points[:, 1] += roi[1]
            landmarks = np.rint(points).astype("uint").tolist()
            batch.setdefault("landmarks", []).append(landmarks)
        logger.trace("Predicted Landmarks: %s", batch["landmarks"]) </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     def get_pts_from_predict(prediction, roi):
 </s> add     def get_pts_from_predict(batch): </s> remove         image = np.expand_dims(np.transpose(feed_dict["image"], (2, 0, 1)), 0).astype("float32")
        self.model.setInput(image)
        prediction = self.model.forward()
        pts_img = self.get_pts_from_predict(prediction, feed_dict["roi"])
        return pts_img
 </s> add         self.model.setInput(batch["feed"])
        batch["prediction"] = self.model.forward()
        return batch

    def process_output(self, batch):
        """ Process the output from the model """
        self.get_pts_from_predict(batch)
        return batch
    def get_pts_from_predict(self, batch):
          retval = [(int(pt[0]), int(pt[1])) for pt in pts_img]
          logger.trace("Predicted Landmarks: %s", retval)
          return retval
  
    def get_pts_from_predict(self, batch):
          """ Get points from predictor """
          logger.trace("Obtain points from prediction")
          var_b = prediction.reshape((prediction.shape[0],
                                      prediction.shape[1] * prediction.shape[2]))
          var_c = var_b.argmax(1).reshape((prediction.shape[0],
keep keep keep keep replace keep keep keep keep keep
 De-Multiprocess Extract (#871)

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

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

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

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

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

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

* Standardize all extract plugins to new threaded codebase

* Documentation. Start implementing Numpy style docstrings for Sphinx Documentation

* Remove s3fd_amd. Change convert OTF to expect DetectedFace object

* faces_detect - clean up and documentation

* Remove PoolProcess

* Migrate manual tool to new extract workflow

* Remove AMD specific extractor code from cli and plugins

* Sort tool to new extract workflow

* Remove multiprocessing from project

* Remove multiprocessing queues from QueueManager

* Remove multiprocessing support from logger

* Move face_filter to new extraction pipeline

* Alignments landmarksXY > landmarks_xy and legacy handling

* Intercept get_backend for sphinx doc build

# Add Sphinx documentation 
plugins/extract/align/fan.py