diff --git a/bob/pad/face/database/maskattack.py b/bob/pad/face/database/maskattack.py
deleted file mode 100644
index a4d8daf72faa908428ff902a4bc0ef452abf387e..0000000000000000000000000000000000000000
--- a/bob/pad/face/database/maskattack.py
+++ /dev/null
@@ -1,238 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-import os
-import numpy as np
-import bob.io.video
-from bob.bio.video import FrameSelector, FrameContainer
-from bob.pad.face.database import VideoPadFile  # Used in MsuMfsdPadFile class
-from bob.pad.base.database import PadDatabase
-
-class MaskAttackPadFile(VideoPadFile):
-    """
-    A high level implementation of the File class for the 3DMAD database.
-    """
-
-    def __init__(self, f):
-        """
-        **Parameters:**
-
-        ``f`` : :py:class:`object`
-            An instance of the File class defined in the low level db interface
-            of the 3DMAD database, in the bob.db.maskattack.models.py file.
-        """
-
-        self.f = f
-        if f.is_real():
-            attack_type = None
-        else:
-            attack_type = 'mask'
-
-        super(MaskAttackPadFile, self).__init__(
-            client_id=f.client_id,
-            path=f.path,
-            attack_type=attack_type,
-            file_id=f.id)
-
-    #==========================================================================
-    def load(self, directory=None, extension='.avi', frame_selector=FrameSelector(selection_style='all')):
-        """
-        Overridden version of the load method defined in the ``VideoPadFile``.
-
-        **Parameters:**
-
-        ``directory`` : :py:class:`str`
-            String containing the path to the MSU MFSD database.
-            Default: None
-
-        ``extension`` : :py:class:`str`
-            Extension of the video files in the MSU MFSD database.
-            Note: ``extension`` value is not used in the code of this method.
-            Default: None
-
-        ``frame_selector`` : ``FrameSelector``
-            The frame selector to use.
-
-        **Returns:**
-
-        ``video_data`` : FrameContainer
-            Video data stored in the FrameContainer, see ``bob.bio.video.utils.FrameContainer``
-            for further details.
-        """
-        vfilename = self.make_path(directory, extension)
-        video = bob.io.video.reader(vfilename)
-        video_data_array = video.load()
-        
-        return frame_selector(video_data_array)
-
-
-#==============================================================================
-class MaskAttackPadDatabase(PadDatabase):
-    """
-    A high level implementation of the Database class for the 3DMAD database.
-    """
-
-    def __init__(
-            self,
-            protocol=None,  
-            original_directory=None,
-            original_extension='.avi',
-            **kwargs):
-        """
-        **Parameters:**
-
-        ``protocol`` : :py:class:`str` or ``None``
-            The name of the protocol that defines the default experimental setup for this database.
-
-        ``original_directory`` : :py:class:`str`
-            The directory where the original data of the database are stored.
-
-        ``original_extension`` : :py:class:`str`
-            The file name extension of the original data.
-
-        ``kwargs``
-            The arguments of the :py:class:`bob.bio.base.database.BioDatabase` base class constructor.
-        """
-
-        from bob.db.maskattack import Database as LowLevelDatabase
-
-        self.db = LowLevelDatabase()
-
-        # Since the high level API expects different group names than what the low
-        # level API offers, you need to convert them when necessary
-        self.low_level_group_names = (
-            'world', 'dev',
-            'test')  # group names in the low-level database interface
-        self.high_level_group_names = (
-            'train', 'dev',
-            'eval')  # names are expected to be like that in objects() function
-
-        # Always use super to call parent class methods.
-        super(MaskAttackPadDatabase, self).__init__(
-            name='maskattack',
-            protocol=protocol,
-            original_directory=original_directory,
-            original_extension=original_extension,
-            **kwargs)
-
-    @property
-    def original_directory(self):
-        return self.db.original_directory
-
-    @original_directory.setter
-    def original_directory(self, value):
-        self.db.original_directory = value
-
-    #==========================================================================
-    def objects(self,
-                groups=None,
-                protocol=None,
-                purposes=None,
-                model_ids=None,
-                **kwargs):
-        """
-        This function returns lists of MaskAttackPadFile objects, which fulfill the given restrictions.
-
-        Keyword parameters:
-
-        ``groups`` : :py:class:`str`
-            OR a list of strings.
-            The groups of which the clients should be returned.
-            Usually, groups are one or more elements of ('train', 'dev', 'eval')
-
-        ``protocol`` : :py:class:`str`
-            The protocol for which the clients should be retrieved.
-            Note: this argument is not used in the code, because ``objects`` method of the
-            low-level BD interface of the MSU MFSD doesn't have ``protocol`` argument.
-
-        ``purposes`` : :py:class:`str`
-            OR a list of strings.
-            The purposes for which File objects should be retrieved.
-            Usually it is either 'real' or 'attack'.
-
-        ``model_ids``
-            This parameter is not supported in PAD databases yet.
-
-        **Returns:**
-
-        ``files`` : [MsuMfsdPadFile]
-            A list of MsuMfsdPadFile objects.
-        """
-
-        # Convert group names to low-level group names here.
-        groups = self.convert_names_to_lowlevel(
-            groups, self.low_level_group_names, self.high_level_group_names)
-        # Since this database was designed for PAD experiments, nothing special
-        # needs to be done here.
-
-        print("Objects method called with groups = {}, protocol = {}, purposes = {}, model_ids = {}".format(groups, protocol, purposes, model_ids))
-        #print("Kwargs -> {}".format(**kwargs))
-        #print("Translated groups = {}".frima)
-        
-        # for training
-
-        # for dev
-
-        # for eval
-        lowlevel_purposes = []
-        if purposes == 'real':
-          lowlevel_purposes = ['trainReal', 'probeReal', 'classifyReal']
-        else:
-          lowlevel_purposes = ['trainMask', 'probeMask', 'classifyMask']
-          
-        #if groups == ['world']:
-        #    lowlevel_purposes = ['trainMask']
-        #  if groups == ['world']:
-        #    lowlevel_purposes = ['trainMask']
-        #print(lowlevel_purposes)
-        files = self.db.objects(sets=groups, purposes=lowlevel_purposes, **kwargs)
-
-        files = [MaskAttackPadFile(f) for f in files]
-
-        return files
-
-    #==========================================================================
-    def annotations(self, file):
-        """
-        Return annotations for a given file object ``f``, which is an instance
-        of ``MsuMfsdPadFile`` defined in the HLDI of the MSU MFSD DB.
-        The ``load()`` method of ``MsuMfsdPadFile`` class (see above)
-        returns a video, therefore this method returns bounding-box annotations
-        for each video frame. The annotations are returned as dictionary of dictionaries.
-
-        **Parameters:**
-
-        ``f`` : :py:class:`object`
-            An instance of ``MsuMfsdPadFile`` defined above.
-
-        **Returns:**
-
-        ``annotations`` : :py:class:`dict`
-            A dictionary containing the annotations for each frame in the video.
-            Dictionary structure: ``annotations = {'1': frame1_dict, '2': frame1_dict, ...}``.
-            Where ``frameN_dict = {'topleft': (row, col), 'bottomright': (row, col)}``
-            is the dictionary defining the coordinates of the face bounding box in frame N.
-        """
-        return None
-
-        #annots = f.f.bbx(
-        #    directory=self.original_directory
-        #)  # numpy array containing the face bounding box data for each video frame, returned data format described in the f.bbx() method of the low level interface
-
-        #annotations = {}  # dictionary to return
-
-        #for frame_annots in annots:
-
-        #    topleft = (np.int(frame_annots[2]), np.int(frame_annots[1]))
-        #    bottomright = (np.int(frame_annots[2] + frame_annots[4]),
-        #                   np.int(frame_annots[1] + frame_annots[3]))
-
-        #    annotations[str(np.int(frame_annots[0]))] = {
-        #        'topleft': topleft,
-        #        'bottomright': bottomright
-        #    }
-
-        #return annotations
-
-    #def model_with_ids_protocol(groups=None, protocol=None):
-    #  pass
diff --git a/bob/pad/face/extractor/LTSS.py b/bob/pad/face/extractor/LTSS.py
index c1077a8888af2d87088d2d900b1ee5731f49decf..a47475d7360007ce03a0296a7b9b73376f8432f0 100644
--- a/bob/pad/face/extractor/LTSS.py
+++ b/bob/pad/face/extractor/LTSS.py
@@ -54,9 +54,12 @@ class LTSS(Extractor, object):
       The length of the signal to consider (in seconds)
     
     """
-    super(LTSS, self).__init__()
+    super(LTSS, self).__init__(**kwargs)
     self.framerate = framerate
+    
+    # TODO: try to use window size as NFFT - Guillaume HEUSCH, 04-07-2018
     self.nfft = nfft
+    
     self.debug = debug
     self.window_size = window_size
     self.concat = concat
@@ -80,27 +83,40 @@ class LTSS(Extractor, object):
 
     # log-magnitude of DFT coefficients
     log_mags = []
-   
+
     # go through windows
     for w in range(0, (signal.shape[0] - self.window_size), window_stride):
+      
+      # n is even, as a consequence the fft is as follows [y(0), Re(y(1)), Im(y(1)), ..., Re(y(n/2))]
+      # i.e. each coefficient, except first and last, is represented by two numbers (real + imaginary)
       fft = rfft(signal[w:w+self.window_size], n=self.nfft)
-      mags = numpy.zeros(int(self.nfft/2), dtype=numpy.float64)
       
-      # XXX : bug was here (no clipping)
+      # the magnitude is the norm of the complex numbers, so its size is n/2 + 1
+      mags = numpy.zeros((int(self.nfft/2) + 1), dtype=numpy.float64)
+      
+      # first coeff is real
       if abs(fft[0]) < 1:
         mags[0] = 1
       else:
         mags[0] = abs(fft[0])
-      # XXX 
 
+      # go through coeffs 2 to n/2
       index = 1
       for i in range(1, (fft.shape[0]-1), 2):
         mags[index] = numpy.sqrt(fft[i]**2 + fft[i+1]**2)
         if mags[index] < 1:
           mags[index] = 1
         index += 1
+      
+      # last coeff is real too
+      if abs(fft[-1]) < 1:
+        mags[index] = 1
+      else:
+        mags[index] = abs(fft[-1])
+      
       log_mags.append(numpy.log(mags))
 
+    # build final feature
     log_mags = numpy.array(log_mags)
     mean = numpy.mean(log_mags, axis=0)
     std = numpy.std(log_mags, axis=0)
diff --git a/bob/pad/face/extractor/LiFeatures.py b/bob/pad/face/extractor/LiSpectralFeatures.py
similarity index 97%
rename from bob/pad/face/extractor/LiFeatures.py
rename to bob/pad/face/extractor/LiSpectralFeatures.py
index 0e25a0b04bf3cc78ba3bb34ab58b25c7a88cec8c..294e0f359ab417c29ee6626fa199ea83b5e066e9 100644
--- a/bob/pad/face/extractor/LiFeatures.py
+++ b/bob/pad/face/extractor/LiSpectralFeatures.py
@@ -9,7 +9,7 @@ from bob.core.log import setup
 logger = setup("bob.pad.face")
 
 
-class LiFeatures(Extractor, object):
+class LiSpectralFeatures(Extractor, object):
   """Compute features from pulse signals in the three color channels.
 
   The features are described in the following article:
@@ -41,7 +41,7 @@ class LiFeatures(Extractor, object):
       Plot stuff
   
     """
-    super(LiFeatures, self).__init__()
+    super(LiSpectralFeatures, self).__init__()
     self.framerate = framerate
     self.nfft = nfft
     self.debug = debug
diff --git a/bob/pad/face/extractor/PPGSecure.py b/bob/pad/face/extractor/PPGSecure.py
index 1865ec69967f4fed4cff73289d153f3258317d57..28b3fe0e89dd4be73dd2a4724fe01d8a607e2854 100644
--- a/bob/pad/face/extractor/PPGSecure.py
+++ b/bob/pad/face/extractor/PPGSecure.py
@@ -72,13 +72,13 @@ class PPGSecure(Extractor, object):
     # get the frequencies
     f = numpy.fft.fftfreq(self.nfft) * self.framerate
    
-    # we have 5x3 pulse signals, in different regions across 3 channels
+    # we have 5 pulse signals, in different regions 
     ffts = numpy.zeros((5, output_dim))
     for i in range(5):
       ffts[i] = abs(numpy.fft.rfft(signal[:, i], n=self.nfft))
 
     fft = numpy.concatenate([ffts[0], ffts[1], ffts[2], ffts[3], ffts[4]])
-      
+
     if self.debug: 
       from matplotlib import pyplot
       pyplot.plot(range(output_dim*5), fft, 'k')
diff --git a/bob/pad/face/extractor/__init__.py b/bob/pad/face/extractor/__init__.py
index 452450f4c8e12a7d295c201e378a35fdac9ef4d0..3a06acf23b98882174cfd32acad0d9932ed5dc8b 100644
--- a/bob/pad/face/extractor/__init__.py
+++ b/bob/pad/face/extractor/__init__.py
@@ -2,7 +2,7 @@ from .LBPHistogram import LBPHistogram
 from .ImageQualityMeasure import ImageQualityMeasure
 from .FrameDiffFeatures import FrameDiffFeatures
 
-from .LiFeatures import LiFeatures 
+from .LiSpectralFeatures import LiSpectralFeatures 
 from .LTSS import LTSS 
 from .PPGSecure import PPGSecure 
 
@@ -28,5 +28,8 @@ __appropriate__(
     LBPHistogram,
     ImageQualityMeasure,
     FrameDiffFeatures,
+    LiSpectralFeatures,
+    LTSS,
+    PPGSecure,
 )
 __all__ = [_ for _ in dir() if not _.startswith('_')]
diff --git a/bob/pad/face/preprocessor/Chrom.py b/bob/pad/face/preprocessor/Chrom.py
index 987492ea9c1d5423c4403aa7e6bc06f48def87be..7e37f61acffb62281154c263d9a4702feda65a45 100644
--- a/bob/pad/face/preprocessor/Chrom.py
+++ b/bob/pad/face/preprocessor/Chrom.py
@@ -81,7 +81,7 @@ class Chrom(Preprocessor, object):
     self.debug = debug
     self.skin_filter = bob.ip.skincolorfilter.SkinColorFilter()
 
-  def __call__(self, frames, annotations):
+  def __call__(self, frames, annotations=None):
     """Computes the pulse signal for the given frame sequence
 
     Parameters
diff --git a/bob/pad/face/preprocessor/Li.py b/bob/pad/face/preprocessor/LiPulseExtraction.py
similarity index 92%
rename from bob/pad/face/preprocessor/Li.py
rename to bob/pad/face/preprocessor/LiPulseExtraction.py
index 5f54a1bb5e14b7a690262bb5a4751b29028d6518..6a4a5899f57a26456e1ca05db0ac532a0162e428 100644
--- a/bob/pad/face/preprocessor/Li.py
+++ b/bob/pad/face/preprocessor/LiPulseExtraction.py
@@ -17,10 +17,15 @@ from bob.rppg.cvpr14.filter_utils import detrend
 from bob.rppg.cvpr14.filter_utils import average
 
 
-class Li(Preprocessor):
+class LiPulseExtraction(Preprocessor):
   """Extract pulse signal from a video sequence.
   
-  The pulse is extracted according to Li's CVPR 14 algorithm.
+  The pulse is extracted according to a simplified version of Li's CVPR 14 algorithm.
+
+  It is described in:
+  X. Li, J, Komulainen, G. Zhao, P-C Yuen and M. Pietikäinen
+  "Generalized face anti-spoofing by detecting pulse from face videos"
+  Intl Conf on Pattern Recognition (ICPR), 2016
 
   See the documentation of `bob.rppg.base`
 
@@ -65,7 +70,7 @@ class Li(Preprocessor):
       Plot some stuff 
 
     """
-    super(Li, self).__init__(**kwargs)
+    super(LiPulseExtraction, self).__init__(**kwargs)
     self.indent = indent
     self.lambda_ = lambda_
     self.window = window
@@ -148,6 +153,8 @@ class Li(Preprocessor):
 
       ldms = numpy.array(ldms)
       mask_points, mask = kp66_to_mask(frame, ldms, self.indent, self.debug)
+      
+      #XXX : be sure that the 3 colors are returned !!
       face_color[i] = compute_average_colors_mask(frame, mask, self.debug)
 
       previous_ldms = ldms 
diff --git a/bob/pad/face/preprocessor/PPGSecure.py b/bob/pad/face/preprocessor/PPGSecure.py
index 36b201d1554368f9911a27f96776d88a067015b0..33a944bd192c11fae2ac2c25cabe31f4ec01f526 100644
--- a/bob/pad/face/preprocessor/PPGSecure.py
+++ b/bob/pad/face/preprocessor/PPGSecure.py
@@ -130,7 +130,7 @@ class PPGSecure(Preprocessor):
       # get the mask and the green value in the different ROIs
       masks = self._get_masks(frame, ldms)
       for k in range(5):
-        green_mean[i, k] = compute_average_colors_mask(frame, masks[k], self.debug)[1]
+        green_mean[i, k] = compute_average_colors_mask(frame, masks[k], self.debug)
 
       previous_ldms = ldms 
 
diff --git a/bob/pad/face/preprocessor/__init__.py b/bob/pad/face/preprocessor/__init__.py
index 651b372cf42308e68357b884b809cedb522e8703..4badf05c20a8e153516d99f8b3a89e5036bfc5b8 100644
--- a/bob/pad/face/preprocessor/__init__.py
+++ b/bob/pad/face/preprocessor/__init__.py
@@ -2,7 +2,7 @@ from .FaceCropAlign import FaceCropAlign
 from .FrameDifference import FrameDifference
 from .VideoSparseCoding import VideoSparseCoding
 
-from .Li import Li
+from .LiPulseExtraction import LiPulseExtraction
 from .Chrom import Chrom
 from .SSR import SSR
 from .PPGSecure import PPGSecure
@@ -29,5 +29,9 @@ __appropriate__(
     FaceCropAlign,
     FrameDifference,
     VideoSparseCoding,
+    LiPulseExtraction,
+    Chrom,
+    SSR,
+    PPGSecure,
 )
 __all__ = [_ for _ in dir() if not _.startswith('_')]
diff --git a/bob/pad/face/test/test.py b/bob/pad/face/test/test.py
index bbcf337a9f4d0488d8af579ca4717901b5bbcad9..f7c7ce29622448a0b655c72e880e0ac7b7871d21 100644
--- a/bob/pad/face/test/test.py
+++ b/bob/pad/face/test/test.py
@@ -28,7 +28,15 @@ from ..extractor import LBPHistogram
 
 from ..extractor import ImageQualityMeasure
 
-import random
+from ..preprocessor import LiPulseExtraction
+from ..preprocessor import Chrom
+from ..preprocessor import PPGSecure as PPGPreprocessor
+from ..preprocessor import SSR
+
+from ..extractor import LTSS
+from ..extractor import LiSpectralFeatures
+from ..extractor import PPGSecure as PPGExtractor
+
 
 from ..preprocessor.FaceCropAlign import detect_face_landmarks_in_image
 
@@ -371,3 +379,99 @@ def convert_array_to_list_of_frame_cont(data):
             frame_container)  # add current frame to FrameContainer
 
     return frame_container_list
+
+
+def test_preprocessor_LiPulseExtraction():
+      """ Test the pulse extraction using Li's ICPR 2016 algorithm.
+      """
+
+      image = load(datafile('test_image.png', 'bob.pad.face.test'))
+      annotations = {'topleft': (95, 155), 'bottomright': (215, 265)}
+      video, annotations = convert_image_to_video_data(image, annotations, 100)
+      
+      preprocessor = LiPulseExtraction(debug=False)
+      pulse = preprocessor(video, annotations)
+      assert pulse.shape == (100, 3)
+
+
+def test_preprocessor_Chrom():
+      """ Test the pulse extraction using CHROM algorithm.
+      """
+
+      image = load(datafile('test_image.png', 'bob.pad.face.test'))
+      annotations = {'topleft': (95, 155), 'bottomright': (215, 265)}
+      video, annotations = convert_image_to_video_data(image, annotations, 100)
+  
+      preprocessor = Chrom(debug=False)
+      pulse = preprocessor(video, annotations)
+      assert pulse.shape[0] == 100
+
+
+def test_preprocessor_PPGSecure():
+      """ Test the pulse extraction using PPGSecure algorithm.
+      """
+
+      image = load(datafile('test_image.png', 'bob.pad.face.test'))
+      annotations = {'topleft': (456, 212), 'bottomright': (770, 500)}
+      video, annotations = convert_image_to_video_data(image, annotations, 100)
+  
+      preprocessor = PPGPreprocessor(debug=False)
+      pulse = preprocessor(video, annotations)
+      assert pulse.shape == (100, 5)
+
+
+def test_preprocessor_SSR():
+      """ Test the pulse extraction using SSR algorithm.
+      """
+      
+      image = load(datafile('test_image.png', 'bob.pad.face.test'))
+      annotations = {'topleft': (95, 155), 'bottomright': (215, 265)}
+      video, annotations = convert_image_to_video_data(image, annotations, 100)
+  
+      preprocessor = SSR(debug=False)
+      pulse = preprocessor(video, annotations)
+      assert pulse.shape[0] == 100
+
+
+def test_extractor_LTSS():
+      """ Test Long Term Spectrum Statistics (LTSS) Feature Extractor 
+      """
+      
+      # "pulse" in 3 color channels
+      data = np.random.random((200, 3))
+      
+      extractor = LTSS(concat=True)
+      features = extractor(data)
+      # n = number of FFT coefficients (default is 64)
+      # (n/2 + 1) * 2 (mean and std) * 3 (colors channels)
+      assert features.shape[0] == 33*2*3
+      
+      extractor = LTSS(concat=False)
+      features = extractor(data)
+      # only one "channel" is considered
+      assert features.shape[0] == 33*2
+
+
+def test_extractor_LiSpectralFeatures():
+      """ Test Li's ICPR 2016 Spectral Feature Extractor 
+      """
+      
+      # "pulse" in 3 color channels
+      data = np.random.random((200, 3))
+      
+      extractor = LiSpectralFeatures()
+      features = extractor(data)
+      assert features.shape[0] == 6 
+     
+
+def test_extractor_PPGSecure():
+      """ Test PPGSecure Spectral Feature Extractor 
+      """
+      # 5 "pulses" 
+      data = np.random.random((200, 5))
+      
+      extractor = PPGExtractor()
+      features = extractor(data)
+      # n = number of FFT coefficients (default is 32)
+      # 5 (pulse signals) * (n/2 + 1)
+      assert features.shape[0] == 5*17
diff --git a/conda/meta.yaml b/conda/meta.yaml
index 39c2553363bbbcb2382c2f504269e8957138b210..ba29310df2b9b246cee748b6d9356420d0e0073d 100644
--- a/conda/meta.yaml
+++ b/conda/meta.yaml
@@ -35,14 +35,13 @@ requirements:
     - bob.learn.libsvm
     - bob.ip.dlib
     - bob.ip.facelandmarks
-    - bob.rppg.base
+    - bob.rppg.base >=2.0.0
   run:
     - python
     - setuptools
     - six
     - numpy >=1.11 
     - scikit-learn
-    - bob.rppg.base
 
 test:
   imports:
diff --git a/doc/index.rst b/doc/index.rst
index c9355ac8776b33d7d603cad9baa4877793ec9993..0fef703937fc291068066e827ef6df565ce6aca4 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -22,6 +22,7 @@ Users Guide
    installation
    baselines
    other_pad_algorithms
+   pulse
    references
    resources
    api
diff --git a/doc/pulse.rst b/doc/pulse.rst
new file mode 100644
index 0000000000000000000000000000000000000000..1e9ddbe95bdb72121aeacb392e8e0d13dfb5c546
--- /dev/null
+++ b/doc/pulse.rst
@@ -0,0 +1,68 @@
+
+.. _bob.pad.face.pulse:
+
+===============
+Pulse-based PAD
+===============
+
+In this section, we briefly describe our work made for face
+presentation attack detection using the blood volume pulse,
+inferred from remote photoplesthymograpy.
+
+The basic idea here is to retrieve the pulse signals from
+face video sequences, to derive features from their frequency
+spectrum and then to learn a classifier to discriminate
+between *bonafide* attempts from presentation attacks.
+
+For this purpose, we describe both :py:class:`bob.bio.base.preprocessor.Preprocessor` and
+:py:class:`bob.bio.base.extractor.Extractor` specifically dedicated to this task.
+
+Preprocessors: Pulse Extraction
+-------------------------------
+
+Preprocessors basically extract pulse signals from face video 
+sequences. They heavily rely on what has been done in `bob.rppg.base`
+so you may want to have a look at `its documentation <https://www.idiap.ch/software/bob/docs/bob/bob.rppg.base/master/index.html>`_. 
+
+In this package, 4 preprocessors have been implemented:
+
+  1. :py:class:`bob.pad.face.preprocessor.LiPulseExtraction` described in [Li_ICPR_2016]_.
+  
+  2. :py:class:`bob.pad.face.preprocessor.Chrom` described in [CHROM]_.
+
+  3. :py:class:`bob.pad.face.preprocessor.SSR` described in [SSR]_.
+
+  4. :py:class:`bob.pad.face.preprocessor.PPGSecure` described in [NOWARA]_.
+
+
+Extractors: Features from Pulses
+--------------------------------
+
+Extractors compute and retrieve features from the pulse signal. All
+implemented extractors act on the frequency spectrum of the pulse signal.
+
+In this package, 3 extractors have been implemented:
+
+  1. :py:class:`bob.pad.face.extractor.LiSpectralFeatures` described in [Li_ICPR_2016]_.
+  
+  2. :py:class:`bob.pad.face.extractor.PPGSecure` described in [NOWARA]_.
+
+  3. :py:class:`bob.pad.face.extractor.LTSS` described in [LTSS]_.
+
+
+
+References
+----------
+
+
+.. [Li_ICPR_2016] *X. Li, J, Komulainen, G. Zhao, P-C Yuen and M. Pietikäinen*
+  **Generalized face anti-spoofing by detecting pulse from face videos**,
+  Intl Conf on Pattern Recognition (ICPR), 2016
+
+.. [CHROM] *de Haan, G. & Jeanne, V*. **Robust Pulse Rate from Chrominance based rPPG**, IEEE Transactions on Biomedical Engineering, 2013. `pdf <http://www.es.ele.tue.nl/~dehaan/pdf/169_ChrominanceBasedPPG.pdf>`__
+
+.. [SSR] *Wang, W., Stuijk, S. and de Haan, G*. **A Novel Algorithm for Remote Photoplesthymograpy: Spatial Subspace Rotation**, IEEE Trans. On Biomedical Engineering, 2015
+
+.. [NOWARA] *E. M. Nowara, A. Sabharwal, A. Veeraraghavan*. **PPGSecure: Biometric Presentation Attack Detection Using Photopletysmograms**, IEEE International Conference on Automatic Face & Gesture Recognition, 2017
+
+.. [LTSS] *H .Muckenhirn, P. Korshunov, M. Magimai-Doss, S Marcel*. **Long-Term Spectral Statistics for Voice Presentation Attack Detection**, IEEE Trans. On Audio, Speech and Language Processing, 2017
diff --git a/requirements.txt b/requirements.txt
index ea78408c78857284bdab6cef684c56b659add5e1..e77e1db4afde0822bf74bacc67fae85a4bbf5a8a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -16,4 +16,4 @@ bob.ip.facelandmarks
 bob.learn.libsvm
 bob.learn.linear
 scikit-learn
-bob.rppg.base
+bob.rppg.base >= 2.0.0