diff --git a/bob/bio/face/algorithm/GaborJet.py b/bob/bio/face/algorithm/GaborJet.py
new file mode 100644
index 0000000000000000000000000000000000000000..7499d5f40d57082a45398158fc784b3ed8fba729
--- /dev/null
+++ b/bob/bio/face/algorithm/GaborJet.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+# vim: set fileencoding=utf-8 :
+# Manuel Guenther <Manuel.Guenther@idiap.ch>
+
+import bob.ip.gabor
+import bob.io.base
+
+import numpy
+import math
+
+from bob.bio.base.algorithm import Algorithm
+
+class GaborJet (Algorithm):
+  """Algorithm chain for computing Gabor jets, Gabor graphs, and Gabor graph comparisons"""
+
+  def __init__(
+      self,
+      # parameters for the tool
+      gabor_jet_similarity_type,
+      multiple_feature_scoring = 'max_jet',
+      # some similarity functions might need a GaborWaveletTransform class, so we have to provide the parameters here as well...
+      gabor_directions = 8,
+      gabor_scales = 5,
+      gabor_sigma = 2. * math.pi,
+      gabor_maximum_frequency = math.pi / 2.,
+      gabor_frequency_step = math.sqrt(.5),
+      gabor_power_of_k = 0,
+      gabor_dc_free = True
+  ):
+
+    # call base class constructor
+    Algorithm.__init__(
+        self,
+
+        gabor_jet_similarity_type = gabor_jet_similarity_type,
+        multiple_feature_scoring = multiple_feature_scoring,
+        gabor_directions = gabor_directions,
+        gabor_scales = gabor_scales,
+        gabor_sigma = gabor_sigma,
+        gabor_maximum_frequency = gabor_maximum_frequency,
+        gabor_frequency_step = gabor_frequency_step,
+        gabor_power_of_k = gabor_power_of_k,
+        gabor_dc_free = gabor_dc_free,
+
+        multiple_model_scoring = None,
+        multiple_probe_scoring = None
+    )
+
+    # the Gabor wavelet transform; used by (some of) the Gabor jet similarities
+    gwt = bob.ip.gabor.Transform(
+        number_of_scales = gabor_scales,
+        number_of_directions = gabor_directions,
+        sigma = gabor_sigma,
+        k_max = gabor_maximum_frequency,
+        k_fac = gabor_frequency_step,
+        power_of_k = gabor_power_of_k,
+        dc_free = gabor_dc_free
+    )
+
+    # jet comparison function
+    self.similarity_function = bob.ip.gabor.Similarity(gabor_jet_similarity_type, gwt)
+
+    # how to proceed with multiple features per model
+    self.jet_scoring = {
+        'average_model' : None, # compute an average model
+        'average' : numpy.average, # compute the average similarity
+        'min_jet' : min, # for each jet location, compute the minimum similarity
+        'max_jet' : max, # for each jet location, compute the maximum similarity
+        'med_jet' : numpy.median, # for each jet location, compute the median similarity
+        'min_graph' : numpy.average, # for each model graph, compute the minimum average similarity
+        'max_graph' : numpy.average, # for each model graph, compute the maximum average similarity
+        'med_graph' : numpy.average, # for each model graph, compute the median average similarity
+    }[multiple_feature_scoring]
+
+    self.graph_scoring = {
+        'average_model' : None, # compute an average model
+        'average' : numpy.average, # compute the average similarity
+        'min_jet' : numpy.average, # for each jet location, compute the minimum similarity
+        'max_jet' : numpy.average, # for each jet location, compute the maximum similarity
+        'med_jet' : numpy.average, # for each jet location, compute the median similarity
+        'min_graph' : min, # for each model graph, compute the minimum average similarity
+        'max_graph' : max, # for each model graph, compute the maximum average similarity
+        'med_graph' : numpy.median, # for each model graph, compute the median average similarity
+    }[multiple_feature_scoring]
+
+
+  def _check_feature(self, feature):
+    assert isinstance(feature, list)
+    assert len(feature)
+    assert all(isinstance(f, bob.ip.gabor.Jet) for f in feature)
+
+  def enroll(self, enroll_features):
+    """Enrolls the model by computing an average graph for each model"""
+    [self._check_feature(feature) for feature in enroll_features]
+    assert len(enroll_features)
+    assert all(len(feature) == len(enroll_features[0]) for feature in enroll_features)
+
+    # re-organize the jets to have a collection of jets per node
+    jets_per_node = [[enroll_features[g][n] for g in range(len(enroll_features))] for n in range(len(enroll_features[0]))]
+
+    if self.jet_scoring is not None:
+      return jets_per_node
+
+    # compute average model, and keep a list with a single jet per node
+    return [[bob.ip.gabor.Jet(jets_per_node[n])] for n in range(len(jets_per_node))]
+
+
+  def save_model(self, model, model_file):
+    """Saves the enrolled model of Gabor jets to file."""
+    f = bob.io.base.HDF5File(model_file, 'w')
+    # several model graphs
+    f.set("NumberOfNodes", len(model))
+    for g in range(len(model)):
+      name = "Node-" + str(g+1)
+      f.create_group(name)
+      f.cd(name)
+      bob.ip.gabor.save_jets(model[g], f)
+      f.cd("..")
+    f.close()
+
+  def read_model(self, model_file):
+    f = bob.io.base.HDF5File(model_file)
+    count = f.get("NumberOfNodes")
+    model = []
+    for g in range(count):
+      name = "Node-" + str(g+1)
+      f.cd(name)
+      model.append(bob.ip.gabor.load_jets(f))
+      f.cd("..")
+    return model
+
+
+  def read_probe(self, probe_file):
+    return bob.ip.gabor.load_jets(bob.io.base.HDF5File(probe_file))
+
+
+  def score(self, model, probe):
+    """Computes the score of the probe and the model"""
+    self._check_feature(probe)
+    [self._check_feature(m) for m in model]
+    assert len(model) == len(probe)
+
+    # select jet score averaging function
+    jet_scoring = numpy.average if self.jet_scoring is None else self.jet_scoring
+    graph_scoring = numpy.average if self.graph_scoring is None else self.graph_scoring
+    local_scores = [jet_scoring([self.similarity_function(m, pro) for m in mod]) for mod, pro in zip(model, probe)]
+    return graph_scoring(local_scores)
+
+
+  def score_for_multiple_probes(self, model, probes):
+    """This function computes the score between the given model graph(s) and several given probe graphs."""
+    [self._check_feature(probe) for probe in probes]
+    graph_scoring = numpy.average if self.graph_scoring is None else self.graph_scoring
+    return graph_scoring([self.score(model, probe) for probe in probes])
diff --git a/bob/bio/face/algorithm/LGBPHS.py b/bob/bio/face/algorithm/LGBPHS.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d060987bc59e15ed50aa6b2c3598bc9817e1874
--- /dev/null
+++ b/bob/bio/face/algorithm/LGBPHS.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+# vim: set fileencoding=utf-8 :
+# Manuel Guenther <Manuel.Guenther@idiap.ch>
+
+import bob.math
+
+import numpy
+
+from bob.bio.base.algorithm import Algorithm
+
+class LGBPHS (Algorithm):
+  """Tool chain for computing local Gabor binary pattern histogram sequences"""
+
+  def __init__(
+      self,
+      distance_function = bob.math.chi_square,
+      is_distance_function = True,
+      multiple_probe_scoring = 'average'
+  ):
+    """Initializes the local Gabor binary pattern histogram sequence tool"""
+
+    # call base class constructor
+    Algorithm.__init__(
+        self,
+
+        distance_function = str(distance_function),
+        is_distance_function = is_distance_function,
+
+        multiple_model_scoring = None,
+        multiple_probe_scoring = multiple_probe_scoring
+    )
+
+    # remember distance function
+    self.distance_function = distance_function
+    self.factor =  -1. if is_distance_function else 1
+
+
+  def _is_sparse(self, feature):
+    assert isinstance(feature, numpy.ndarray)
+    return feature.ndim == 2
+
+  def _check_feature(self, feature, sparse):
+    assert isinstance(feature, numpy.ndarray)
+    if sparse:
+      # check that we have a 2D array
+      assert feature.ndim == 2
+      assert feature.shape[0] == 2
+    else:
+      assert feature.ndim == 1
+
+
+  def enroll(self, enroll_features):
+    """Enrolling model by taking the average of all features"""
+    assert len(enroll_features)
+    sparse = self._is_sparse(enroll_features[0])
+    [self._check_feature(feature, sparse) for feature in enroll_features]
+
+    if sparse:
+      # get all indices for the sparse model
+      values = {}
+      # iterate through all sparse features
+      for feature in enroll_features:
+        # collect the values by index
+        for j in range(feature.shape[1]):
+          index = int(feature[0,j])
+          value = feature[1,j] / float(len(enroll_features))
+          # add up values
+          if index in values:
+            values[index] += value
+          else:
+            values[index] = value
+
+      # create model containing all the used indices
+      model = numpy.ndarray((2, len(values)), dtype = numpy.float64)
+      for i, index in enumerate(sorted(values.keys())):
+        model[0,i] = index
+        model[1,i] = values[index]
+    else:
+      model = numpy.zeros(enroll_features[0].shape, dtype = numpy.float64)
+      # add up models
+      for feature in enroll_features:
+        model += feature
+      # normalize by number of models
+      model /= float(len(enroll_features))
+
+    # return averaged model
+    return model
+
+
+  def score(self, model, probe):
+    """Computes the score using the specified histogram measure; returns a similarity value (bigger -> better)"""
+    sparse = self._is_sparse(probe)
+    self._check_feature(model, sparse)
+    self._check_feature(probe, sparse)
+
+    if sparse:
+      # assure that the probe is sparse as well
+      return self.factor * self.distance_function(model[0,:], model[1,:], probe[0,:], probe[1,:])
+    else:
+      return self.factor * self.distance_function(model, probe)
diff --git a/bob/bio/face/algorithm/__init__.py b/bob/bio/face/algorithm/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..63b1837764b38cf36b379c803ca210820c4ec9d1 100644
--- a/bob/bio/face/algorithm/__init__.py
+++ b/bob/bio/face/algorithm/__init__.py
@@ -0,0 +1,2 @@
+from .GaborJet import GaborJet
+from .LGBPHS import LGBPHS
diff --git a/bob/bio/face/config/algorithm/__init__.py b/bob/bio/face/config/algorithm/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bob/bio/face/config/algorithm/gabor_jet.py b/bob/bio/face/config/algorithm/gabor_jet.py
new file mode 100644
index 0000000000000000000000000000000000000000..15c03fcb7a113c7c33b98a158d8ccbc63bc914fb
--- /dev/null
+++ b/bob/bio/face/config/algorithm/gabor_jet.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+
+import bob.bio.face
+import math
+
+algorithm = bob.bio.face.algorithm.GaborJet(
+    # Gabor jet comparison
+    gabor_jet_similarity_type = 'PhaseDiffPlusCanberra',
+    multiple_feature_scoring = 'max_jet',
+    # Gabor wavelet setup
+    gabor_sigma = math.sqrt(2.) * math.pi,
+
+)
diff --git a/bob/bio/face/config/algorithm/lgbphs.py b/bob/bio/face/config/algorithm/lgbphs.py
new file mode 100644
index 0000000000000000000000000000000000000000..635647ac09c0c3968d624e6ab8dad3c7ee2b9c34
--- /dev/null
+++ b/bob/bio/face/config/algorithm/lgbphs.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+
+import bob.bio.face
+import bob.math
+
+algorithm = bob.bio.face.algorithm.LGBPHS(
+    distance_function = bob.math.histogram_intersection,
+    is_distance_function = False
+)
diff --git a/bob/bio/face/test/test_algorithms.py b/bob/bio/face/test/test_algorithms.py
new file mode 100644
index 0000000000000000000000000000000000000000..4df36601c1c056eeef4c9f00989e6b96ad0d3464
--- /dev/null
+++ b/bob/bio/face/test/test_algorithms.py
@@ -0,0 +1,171 @@
+#!/usr/bin/env python
+# vim: set fileencoding=utf-8 :
+# @author: Manuel Guenther <Manuel.Guenther@idiap.ch>
+# @date: Thu May 24 10:41:42 CEST 2012
+#
+# Copyright (C) 2011-2012 Idiap Research Institute, Martigny, Switzerland
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import bob.io.base
+import bob.ip.gabor
+
+import unittest
+import os
+import numpy
+import math
+import tempfile
+import facereclib
+from nose.plugins.skip import SkipTest
+
+import pkg_resources
+
+regenerate_refs = False
+seed_value = 5489
+
+
+def test_gabor_jet():
+  jets = bob.bio.base.load_resource("gabor-jet", "algorithm")
+  assert isinstance(jets, bob.bio.face.algorithm.GaborJet)
+  assert isinstance(jets, bob.bio.base.algorithm.Algorithm)
+  assert not jets.performs_projection
+  assert not jets.requires_projector_training
+  assert not jets.use_projected_features_for_enrollment
+  assert not jets.split_training_features_by_client
+  assert not jets.requires_enroller_training
+
+  # read input
+  feature = bob.ip.gabor.load_jets(bob.io.base.HDF5File(pkg_resources.resource_filename("bob.bio.face.test", "data/graph_regular.hdf5")))
+
+  # enroll
+  model = jets.enroll([feature, feature])
+  assert len(model) == len(feature)
+  assert all(len(m) == 2 for m in model)
+  assert all(model[n][i] == feature[n] for n in range(len(feature)) for i in range(2))
+
+  # score
+  assert abs(jets.score(model, feature) - 1.) < 1e-8
+  assert abs(jets.score_for_multiple_probes(model, [feature, feature]) - 1.) < 1e-8
+
+
+  # test averaging
+  jets = bob.bio.face.algorithm.GaborJet(
+    "PhaseDiffPlusCanberra",
+    multiple_feature_scoring = "average_model"
+  )
+  model = jets.enroll([feature, feature])
+  assert len(model) == len(feature)
+  assert all(len(m) == 1 for m in model)
+
+  # absoulte values must be identical
+  assert all(numpy.allclose(model[n][0].abs, feature[n].abs) for n in range(len(model)))
+  # phases might differ with 2 Pi
+  for n in range(len(model)):
+    for j in range(len(model[n][0].phase)):
+      assert any(abs(model[n][0].phase[j] - feature[n].phase[j] - k*2.*math.pi) < 1e-5 for k in (0, -2, 2))
+
+  assert abs(jets.score(model, feature) - 1.) < 1e-8
+  assert abs(jets.score_for_multiple_probes(model, [feature, feature]) - 1.) < 1e-8
+
+
+def test_lgbphs():
+  lgbphs = bob.bio.base.load_resource("lgbphs", "algorithm")
+  assert isinstance(lgbphs, bob.bio.face.algorithm.LGBPHS)
+  assert isinstance(lgbphs, bob.bio.base.algorithm.Algorithm)
+  assert not lgbphs.performs_projection
+  assert not lgbphs.requires_projector_training
+  assert not lgbphs.use_projected_features_for_enrollment
+  assert not lgbphs.split_training_features_by_client
+  assert not lgbphs.requires_enroller_training
+
+  # read input
+  feature1 = bob.bio.base.load(pkg_resources.resource_filename('bob.bio.face.test', 'data/lgbphs_sparse.hdf5'))
+  feature2 = bob.bio.base.load(pkg_resources.resource_filename('bob.bio.face.test', 'data/lgbphs_with_phase.hdf5'))
+
+  # enroll model from sparse features
+  model1 = lgbphs.enroll([feature1, feature1])
+  assert model1.shape == feature1.shape
+  assert numpy.allclose(model1, feature1)
+
+  # enroll from non-sparse features
+  model2 = lgbphs.enroll([feature2, feature2])
+  assert model2.shape == feature2.shape
+  assert numpy.allclose(model2, feature2)
+
+  # score without phase and sparse
+  reference = 40960.
+  assert abs(lgbphs.score(model1, feature1) - reference) < 1e-5
+  assert abs(lgbphs.score_for_multiple_probes(model1, [feature1, feature1]) - reference) < 1e-5
+
+  # score with phase, but non-sparse
+  # reference doubles since we have two times more features
+  reference *= 2.
+  assert abs(lgbphs.score(model2, feature2) - reference) < 1e-5
+  assert abs(lgbphs.score_for_multiple_probes(model2, [feature2, feature2]) - reference) < 1e-5
+
+
+"""
+  def test09_plda(self):
+    # read input
+    feature = facereclib.utils.load(self.input_dir('linearize.hdf5'))
+    # assure that the config file is readable
+    tool = self.config('pca+plda')
+    self.assertTrue(isinstance(tool, facereclib.tools.PLDA))
+
+    # here, we use a reduced complexity for test purposes
+    tool = facereclib.tools.PLDA(
+        subspace_dimension_of_f = 2,
+        subspace_dimension_of_g = 2,
+        subspace_dimension_pca = 10,
+        plda_training_iterations = 1,
+        INIT_SEED = seed_value,
+    )
+    self.assertFalse(tool.performs_projection)
+    self.assertTrue(tool.requires_enroller_training)
+
+    # train the projector
+    t = tempfile.mkstemp('pca+plda.hdf5', prefix='frltest_')[1]
+    tool.train_enroller(facereclib.utils.tests.random_training_set_by_id(feature.shape, count=20, minimum=0., maximum=255.), t)
+    if regenerate_refs:
+      import shutil
+      shutil.copy2(t, self.reference_dir('pca+plda_enroller.hdf5'))
+
+    # load the projector file
+    tool.load_enroller(self.reference_dir('pca+plda_enroller.hdf5'))
+    # compare the resulting machines
+    test_file = bob.io.base.HDF5File(t)
+    test_file.cd('/pca')
+    pca_machine = bob.learn.linear.Machine(test_file)
+    test_file.cd('/plda')
+    plda_machine = bob.learn.em.PLDABase(test_file)
+    # TODO: compare the PCA machines
+    #self.assertEqual(pca_machine, tool.m_pca_machine)
+    # TODO: compare the PLDA machines
+    #self.assertEqual(plda_machine, tool.m_plda_base_machine)
+    os.remove(t)
+
+    # enroll model
+    model = tool.enroll([feature])
+    if regenerate_refs:
+      model.save(bob.io.base.HDF5File(self.reference_dir('pca+plda_model.hdf5'), 'w'))
+    # TODO: compare the models with the reference
+    #reference_model = tool.read_model(self.reference_dir('pca+plda_model.hdf5'))
+    #self.assertEqual(model, reference_model)
+
+    # score
+    sim = tool.score(model, feature)
+    self.assertAlmostEqual(sim, 0.)
+    # score with a concatenation of the probe
+    self.assertAlmostEqual(tool.score_for_multiple_probes(model, [feature, feature]), 0.)
+
+"""
diff --git a/setup.py b/setup.py
index 6ec602bbdcf7f8d2c58be227582960dbed31ca6d..0c923fcfc7dae289ac14861789da1e9c85de8632 100644
--- a/setup.py
+++ b/setup.py
@@ -127,6 +127,8 @@ setup(
       ],
 
       'bob.bio.algorithm': [
+        'gabor-jet         = bob.bio.face.config.algorithm.gabor_jet:algorithm', # Gabor jet comparison
+        'lgbphs            = bob.bio.face.config.algorithm.lgbphs:algorithm', # LGBPHS histograms
       ],
    },