diff --git a/bob/pad/face/database/batl.py b/bob/pad/face/database/batl.py
index 8452baf74d4ee26f3845dcd7225ee4bb2333bf3f..0ff725d4ea8b963c354455f7b1085641bca264ba 100644
--- a/bob/pad/face/database/batl.py
+++ b/bob/pad/face/database/batl.py
@@ -17,6 +17,14 @@ import bob.io.base
 
 import pkg_resources
 
+map_file = pkg_resources.resource_filename('bob.pad.face', 'lists/batl/idiap_converter_v2.json')
+
+with open(map_file, 'r') as fp:
+    map_dict=json.load(fp)
+
+two_d_attacks=['prints','replay']
+three_d_attacks=['fakehead','glasses','flexiblemask','papermask','rigidmask','makeup']
+BATL_FRAME_SHAPE = (3, 1920, 1080)
 
 # =============================================================================
 class BatlPadFile(PadFile):
@@ -77,7 +85,16 @@ class BatlPadFile(PadFile):
 
         self.f = f
         if f.is_attack():
-            attack_type = 'attack'
+            pai_desc=map_dict["_"+"_".join(os.path.split(f.path)[-1].split("_")[-2:])].split("_")[-1]
+
+            if pai_desc in two_d_attacks:
+                category='2D'
+            elif pai_desc in three_d_attacks:
+                category='3D'
+            else:
+                category='Unknown'
+
+            attack_type = 'attack/' + category +'/'+pai_desc
         else:
             attack_type = None
 
@@ -160,6 +177,99 @@ class BatlPadFile(PadFile):
 
         return data_all_streams
 
+    @property
+    def annotations(self):
+        file_path = os.path.join(self.annotations_temp_dir, self.f.path + ".json")
+
+        if not os.path.isfile(file_path):  # no file with annotations
+
+            try:
+                # original values of the arguments of f:
+                stream_type_original = self.stream_type
+                reference_stream_type_original = self.reference_stream_type
+                warp_to_reference_original = self.warp_to_reference
+                convert_to_rgb_original = self.convert_to_rgb
+                crop_original = self.crop
+                video_data_only_original = self.video_data_only
+                self.stream_type = "color"
+                self.reference_stream_type = "color"
+                self.warp_to_reference = False
+                self.convert_to_rgb = False
+                self.crop = None
+                self.video_data_only = True
+
+                video = self.load(directory=self.original_directory,
+                                  extension=self.original_extension)
+
+            finally:
+                # set arguments of f to the original values:
+                self.stream_type = stream_type_original
+                self.reference_stream_type = reference_stream_type_original
+                self.warp_to_reference = warp_to_reference_original
+                self.convert_to_rgb = convert_to_rgb_original
+                self.crop = crop_original
+                self.video_data_only = video_data_only_original
+
+            annotations = {}
+
+            for idx, image in enumerate(video.as_array()):
+
+                frame_annotations = detect_face_landmarks_in_image(image, method=self.landmark_detect_method)
+
+                if frame_annotations:
+
+                    annotations[str(idx)] = frame_annotations
+
+            if self.annotations_temp_dir:  # if directory is not an empty string
+
+                bob.io.base.create_directories_safe(directory=os.path.split(file_path)[0], dryrun=False)
+
+                with open(file_path, 'w+') as json_file:
+
+                    json_file.write(json.dumps(annotations))
+
+        else:  # if file with annotations exists load them from file
+
+            with open(file_path, 'r') as json_file:
+
+                annotations = json.load(json_file)
+
+        if not annotations:  # if dictionary is empty
+
+            return None
+
+        # If specified append annotations for the roi in the facial region:
+        if self.append_color_face_roi_annot:
+
+            file_path = pkg_resources.resource_filename('bob.pad.face', os.path.join('lists/batl/color_skin_non_skin_annotations/', "annotations_train_dev_set" + ".json"))
+
+            with open(file_path, 'r') as json_file: # open the file containing all annotations:
+
+                roi_annotations = json.load(json_file) # load the annotations
+
+            if not self.f.path in roi_annotations: # no annotations for this file
+
+                return None
+
+            else: # annotations are available
+
+                annotations['face_roi'] = roi_annotations[self.f.path]
+
+        return annotations
+
+    @property
+    def frames(self):
+        frame_container = self.load(directory=self.original_directory, extension=self.original_extension)
+        return frame_container.as_array()
+
+    @property
+    def number_of_frames(self):
+        return len(self.frames)
+
+    @property
+    def frame_shape(self):
+        return BATL_FRAME_SHAPE
+
 
 # =============================================================================
 class BatlPadDatabase(PadDatabase):
@@ -173,7 +283,8 @@ class BatlPadDatabase(PadDatabase):
             protocol='grandtest',
             original_directory=rc['bob.db.batl.directory'],
             original_extension='.h5',
-            annotations_temp_dir=rc['bob.pad.face.database.batl.annotations_temp_dir'],
+            # annotations_temp_dir=rc['bob.pad.face.database.batl.annotations_temp_dir'],
+            annotations_temp_dir=None,
             landmark_detect_method="mtcnn",
             exclude_attacks_list=['makeup'],
             exclude_pai_all_sets=True,
@@ -196,7 +307,7 @@ class BatlPadDatabase(PadDatabase):
             "grandtest-depth-5" - baseline protocol, depth data only,
             use 5 first frames.
 
-            "grandtest-color" - baseline protocol, depth data only, use all frames.
+            "grandtest-color" - baseline protocol, color data only, use all frames.
 
             "grandtest-infrared-50-join_train_dev" - baseline protocol,
             infrared data only, use 50 frames, join train and dev sets forming
@@ -205,9 +316,9 @@ class BatlPadDatabase(PadDatabase):
             "grandtest-infrared-50-LOO_<unseen_attack>", for example "grandtest-infrared-50-LOO_fakehead"
             - Leave One Out (LOO) protocol with fakehead attacks present only in the `test` set. The original partitioning
             in the `grandtest` protocol is queried first and subselects the file list such
-            that the specified `unknown_attack` is removed from both `train` and `dev` sets. 
+            that the specified `unknown_attack` is removed from both `train` and `dev` sets.
             The `test` set will consist of only the selected `unknown_attack` and `bonafide` files.
-            This protocol is used to evaluate the robustness against attacks unseen in training. 
+            This protocol is used to evaluate the robustness against attacks unseen in training.
             .
 
             "grandtest-color*infrared-50" - baseline protocol,
@@ -314,9 +425,9 @@ class BatlPadDatabase(PadDatabase):
 
     def unseen_attack_list_maker(self,files,unknown_attack,train=True):
         """
-        Selects and returns a list of files for Leave One Out (LOO) protocols. 
-        This utilizes the original partitioning in the `grandtest` protocol and subselects 
-        the file list such that the specified `unknown_attack` is removed from both `train` and `dev` sets. 
+        Selects and returns a list of files for Leave One Out (LOO) protocols.
+        This utilizes the original partitioning in the `grandtest` protocol and subselects
+        the file list such that the specified `unknown_attack` is removed from both `train` and `dev` sets.
         The `test` set will consist of only the selected `unknown_attack` and `bonafide` files.
 
         **Parameters:**
@@ -344,14 +455,14 @@ class BatlPadDatabase(PadDatabase):
             attack_category=self.map_dict["_"+"_".join(os.path.split(file.path)[-1].split("_")[-2:])].split("_")[-1]
 
             if train:
-                if  attack_category==unknown_attack:
+                if  attack_category in unknown_attack:
                     pass
                 else:
-                    mod_files.append(file) # everything except the attack specified is there 
+                    mod_files.append(file) # everything except the attack specified is there
 
             if not train:
 
-                if  attack_category==unknown_attack or attack_category=='bonafide':
+                if  attack_category in unknown_attack or attack_category=='bonafide':
                     mod_files.append(file) # only the attack mentioned and bonafides in testing
                 else:
                     pass
@@ -365,7 +476,7 @@ class BatlPadDatabase(PadDatabase):
         An example of protocols it can parse:
         "grandtest-color-5" - grandtest protocol, color data only, use 5 first frames.
         "grandtest-depth-5" - grandtest protocol, depth data only, use 5 first frames.
-        "grandtest-color" - grandtest protocol, depth data only, use all frames.
+        "grandtest-color" - grandtest protocol, color data only, use all frames.
 
         **Parameters:**
 
@@ -392,7 +503,7 @@ class BatlPadDatabase(PadDatabase):
             forming a single training set.
         """
 
-        possible_extras = ['join_train_dev','LOO_fakehead','LOO_flexiblemask','LOO_glasses','LOO_papermask','LOO_prints','LOO_replay','LOO_rigidmask','LOO_makeup']
+        possible_extras = ['join_train_dev','LOO_fakehead','LOO_flexiblemask','LOO_glasses','LOO_papermask','LOO_prints','LOO_replay','LOO_rigidmask','LOO_makeup','PrintReplay']
 
         components = protocol.split("-")
 
@@ -537,7 +648,6 @@ class BatlPadDatabase(PadDatabase):
         ``files`` : [BatlPadFile]
             A list of BATLPadFile objects.
         """
-
         if protocol is None:
             protocol = self.protocol
 
@@ -555,7 +665,6 @@ class BatlPadDatabase(PadDatabase):
         # 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)
 
-            
 
         if not isinstance(groups, list) and groups is not None and not isinstance(groups,str):  # if a single group is given make it a list
             groups = list(groups)
@@ -597,7 +706,7 @@ class BatlPadDatabase(PadDatabase):
                 batl_files=batl_files+tbatl_files
 
 
-            if 'validation' in groups: 
+            if 'validation' in groups:
                 tbatl_files = self.db.objects(protocol=protocol,
                                         groups=['validation'],
                                         purposes=purposes, **kwargs)
@@ -621,6 +730,49 @@ class BatlPadDatabase(PadDatabase):
 
 
 
+        # for the PrintReplay protocol
+        elif extra == "PrintReplay":
+
+            batl_files=[]
+
+            # remove all attacks except replay and print
+            unknown_attack=["fakehead","flexiblemask","glasses","papermask","rigidmask","makeup"]
+
+            if 'train' in groups:
+                tbatl_files = self.db.objects(protocol=protocol,
+                                        groups=['train'],
+                                        purposes=purposes, **kwargs)
+
+                tbatl_files=self.unseen_attack_list_maker(tbatl_files,unknown_attack,train=True)
+
+                batl_files=batl_files+tbatl_files
+
+
+            if 'validation' in groups:
+                tbatl_files = self.db.objects(protocol=protocol,
+                                        groups=['validation'],
+                                        purposes=purposes, **kwargs)
+
+                tbatl_files=self.unseen_attack_list_maker(tbatl_files,unknown_attack,train=True)
+
+
+                batl_files=batl_files+tbatl_files
+
+            if 'test' in groups:
+                tbatl_files = self.db.objects(protocol=protocol,
+                                        groups=['test'],
+                                        purposes=purposes, **kwargs)
+
+                # train=True since we want to remove all attacks except replay and print
+                tbatl_files=self.unseen_attack_list_maker(tbatl_files,unknown_attack,train=True)
+
+                batl_files=batl_files+tbatl_files
+
+
+            files=batl_files
+
+
+
         else:
 #            files = self._fix_funny_eyes_in_objects(protocol=protocol,
 #                                                    groups=groups,
@@ -644,10 +796,15 @@ class BatlPadDatabase(PadDatabase):
                 files = [f for f in files if os.path.split(f.path)[-1].split("_")[-2:-1][0] != "5"]
 
         files = [BatlPadFile(f, stream_type, max_frames) for f in files]
+        for f in files:
+            f.original_directory = self.original_directory
+            f.original_extension = self.original_extension
+            f.annotations_temp_dir = self.annotations_temp_dir
+            f.append_color_face_roi_annot = self.append_color_face_roi_annot
+            f.landmark_detect_method = self.landmark_detect_method
 
         return files
 
-
     def annotations(self, f):
         """
         Computes annotations for a given file object ``f``, which
@@ -674,82 +831,4 @@ class BatlPadDatabase(PadDatabase):
             ``frameN_dict`` contains coordinates of the
             face bounding box and landmarks in frame N.
         """
-
-        file_path = os.path.join(self.annotations_temp_dir, f.f.path + ".json")
-
-        if not os.path.isfile(file_path):  # no file with annotations
-
-            # original values of the arguments of f:
-            stream_type_original = f.stream_type
-            reference_stream_type_original = f.reference_stream_type
-            warp_to_reference_original = f.warp_to_reference
-            convert_to_rgb_original = f.convert_to_rgb
-            crop_original = f.crop
-            video_data_only_original = f.video_data_only
-
-            f.stream_type = "color"
-            f.reference_stream_type = "color"
-            f.warp_to_reference = False
-            f.convert_to_rgb = False
-            f.crop = None
-            f.video_data_only = True
-
-            video = f.load(directory=self.original_directory,
-                           extension=self.original_extension)
-
-            # set arguments of f to the original values:
-            f.stream_type = stream_type_original
-            f.reference_stream_type = reference_stream_type_original
-            f.warp_to_reference = warp_to_reference_original
-            f.convert_to_rgb = convert_to_rgb_original
-            f.crop = crop_original
-            f.video_data_only = video_data_only_original
-
-            annotations = {}
-
-            for idx, image in enumerate(video.as_array()):
-
-                frame_annotations = detect_face_landmarks_in_image(image, method=self.landmark_detect_method)
-
-                if frame_annotations:
-
-                    annotations[str(idx)] = frame_annotations
-
-            if self.annotations_temp_dir:  # if directory is not an empty string
-
-                bob.io.base.create_directories_safe(directory=os.path.split(file_path)[0], dryrun=False)
-
-                with open(file_path, 'w+') as json_file:
-
-                    json_file.write(json.dumps(annotations))
-
-        else:  # if file with annotations exists load them from file
-
-            with open(file_path, 'r') as json_file:
-
-                annotations = json.load(json_file)
-
-        if not annotations:  # if dictionary is empty
-
-            return None
-
-        # If specified append annotations for the roi in the facial region:
-        if self.append_color_face_roi_annot:
-
-            file_path = pkg_resources.resource_filename( 'bob.pad.face', os.path.join('lists/batl/color_skin_non_skin_annotations/', "annotations_train_dev_set" + ".json") )
-
-            with open(file_path, 'r') as json_file: # open the file containing all annotations:
-
-                roi_annotations = json.load(json_file) # load the annotations
-
-            if not f.f.path in roi_annotations: # no annotations for this file
-
-                return None
-
-            else: # annotations are available
-
-                annotations['face_roi'] = roi_annotations[f.f.path]
-
-        return annotations
-
-
+        return f.annotations
diff --git a/bob/pad/face/database/casiafasd.py b/bob/pad/face/database/casiafasd.py
index 7f8e5ca3b999f1c027844911ad0d1e57764fa0df..f3aea4406060bfca1a287408702c134e7c955ec9 100644
--- a/bob/pad/face/database/casiafasd.py
+++ b/bob/pad/face/database/casiafasd.py
@@ -8,6 +8,8 @@ from bob.pad.base.database import PadDatabase
 from bob.pad.face.database import VideoPadFile
 from bob.db.base.utils import (
     check_parameter_for_validity, check_parameters_for_validity)
+from bob.db.base.annotations import read_annotation_file
+from bob.ip.facedetect import expected_eye_positions, BoundingBox
 import numpy
 import os
 
@@ -20,7 +22,7 @@ class CasiaFasdPadFile(VideoPadFile):
     A high level implementation of the File class for the CASIA_FASD database.
     """
 
-    def __init__(self, f, original_directory=None):
+    def __init__(self, f, original_directory=None, annotation_directory=None):
         """
         Parameters
         ----------
@@ -31,6 +33,7 @@ class CasiaFasdPadFile(VideoPadFile):
 
         self.f = f
         self.original_directory = original_directory
+        self.annotation_directory = annotation_directory
 
         if f.is_real():
             attack_type = None
@@ -93,6 +96,10 @@ class CasiaFasdPadFile(VideoPadFile):
     def annotations(self):
         """Reads the annotations
 
+        If the file object has an attribute of annotation_directory, it will read
+        annotations from there instead of loading annotations that are shipped with the
+        database.
+
         Returns
         -------
         annotations : :py:class:`dict`
@@ -103,6 +110,10 @@ class CasiaFasdPadFile(VideoPadFile):
             is the dictionary defining the coordinates of the face bounding box
             in frame N.
         """
+        if self.annotation_directory is not None:
+            path = self.make_path(self.annotation_directory, extension=".json")
+            return read_annotation_file(path, annotation_type="json")
+
         annots = self.f.bbx()
         annotations = {}
         for i, v in enumerate(annots):
@@ -110,6 +121,9 @@ class CasiaFasdPadFile(VideoPadFile):
             bottomright = (v[2] + v[4], v[1] + v[3])
             annotations[str(i)] = {'topleft': topleft,
                                    'bottomright': bottomright}
+            size = (bottomright[0] - topleft[0], bottomright[1] - topleft[1])
+            bounding_box = BoundingBox(topleft, size)
+            annotations[str(i)].update(expected_eye_positions(bounding_box))
         return annotations
 
     def load(self, directory=None, extension='.avi',
@@ -149,6 +163,7 @@ class CasiaFasdPadDatabase(PadDatabase):
             # grandtest is the new modified protocol for this database
             protocol='grandtest',
             original_directory=rc['bob.db.casia_fasd.directory'],
+            annotation_directory=None,
             **kwargs):
         """
         Parameters
@@ -169,6 +184,7 @@ class CasiaFasdPadDatabase(PadDatabase):
             protocol=protocol,
             original_directory=original_directory,
             original_extension='.avi',
+            annotation_directory=annotation_directory,
             training_depends_on_protocol=True,
             **kwargs)
 
@@ -280,7 +296,8 @@ class CasiaFasdPadDatabase(PadDatabase):
                                     db_mappings[t + '_' + q])
                                 files.append(CasiaFasdPadFile(
                                     File(filename, c, grp),
-                                    self.original_directory))
+                                    original_directory=self.original_directory,
+                                    annotation_directory=self.annotation_directory))
         return files
 
     def annotations(self, padfile):
diff --git a/bob/pad/face/database/database.py b/bob/pad/face/database/database.py
index 8f88445f8d0ff4614ddd6d8d24fffde186bfa1b3..40d59d2298ce2d67c961d5221e6200572648c661 100644
--- a/bob/pad/face/database/database.py
+++ b/bob/pad/face/database/database.py
@@ -1,5 +1,7 @@
 from bob.pad.base.database import PadFile
 import bob.bio.video
+import bob.io.video
+from bob.db.base.annotations import read_annotation_file
 
 
 class VideoPadFile(PadFile):
@@ -9,14 +11,15 @@ class VideoPadFile(PadFile):
 
     def __init__(self, attack_type, client_id, path, file_id=None):
         super(VideoPadFile, self).__init__(
-            attack_type=attack_type,
-            client_id=client_id,
-            path=path,
-            file_id=file_id,
+            attack_type=attack_type, client_id=client_id, path=path, file_id=file_id
         )
 
-    def load(self, directory=None, extension='.avi',
-             frame_selector=bob.bio.video.FrameSelector(selection_style='all')):
+    def load(
+        self,
+        directory=None,
+        extension=".avi",
+        frame_selector=bob.bio.video.FrameSelector(selection_style="all"),
+    ):
         """Loads the video file and returns in a :any:`bob.bio.video.FrameContainer`.
 
         Parameters
@@ -34,3 +37,97 @@ class VideoPadFile(PadFile):
             The loaded frames inside a frame container.
         """
         return frame_selector(self.make_path(directory, extension))
+
+    def check_original_directory_and_extension(self):
+        if not hasattr(self, "original_directory"):
+            raise RuntimeError(
+                "Please set the original_directory attribute of files in your "
+                "database's object method."
+            )
+        if not hasattr(self, "original_extension"):
+            raise RuntimeError(
+                "Please set the original_extension attribute of files in your "
+                "database's object method."
+            )
+
+    @property
+    def frames(self):
+        """Returns an iterator of frames in the video.
+        If your database video files need to be loaded in a special way, you need to
+        override this property.
+
+        Returns
+        -------
+        collection.Iterator
+            An iterator returning frames of the video.
+
+        Raises
+        ------
+        RuntimeError
+            In your database implementation, the original_directory and
+            original_extension attributes of the files need to be set when database's
+            object method is called.
+        """
+        self.check_original_directory_and_extension()
+        path = self.make_path(
+            directory=self.original_directory, extension=self.original_extension
+        )
+        return iter(bob.io.video.reader(path))
+
+    @property
+    def number_of_frames(self):
+        self.check_original_directory_and_extension()
+        path = self.make_path(
+            directory=self.original_directory, extension=self.original_extension
+        )
+        return bob.io.video.reader(path).number_of_frames
+
+    @property
+    def frame_shape(self):
+        """Returns the size of each frame in this database.
+        This implementation assumes all videos and frames have the same shape.
+        It's best to override this method in your database implementation and return
+        a constant.
+
+        Returns
+        -------
+        (int, int, int)
+            The (Channels, Height, Width) sizes.
+        """
+        self.check_original_directory_and_extension()
+        path = self.make_path(
+            directory=self.original_directory, extension=self.original_extension
+        )
+        frame = next(bob.io.video.reader(path))
+        return frame.shape
+
+    @property
+    def annotations(self):
+        """Reads the annotations
+        For this property to work, you need to set ``annotation_directory``,
+        ``annotation_extension``, and ``annotation_type`` attributes of the files when
+        database's object method is called.
+
+        Returns
+        -------
+        dict
+            The annotations as a dictionary.
+        """
+        if not (
+            hasattr(self, "annotation_directory")
+            and hasattr(self, "annotation_extension")
+            and hasattr(self, "annotation_type")
+        ):
+            raise RuntimeError(
+                "For this property to work, you need to set ``annotation_directory``, "
+                "``annotation_extension``, and ``annotation_type`` attributes of the "
+                "files when database's object method is called."
+            )
+
+        if self.annotation_directory is None:
+            return None
+
+        annotation_file = self.make_path(
+            directory=self.annotation_directory, extension=self.annotation_extension
+        )
+        return read_annotation_file(annotation_file, self.annotation_type)
diff --git a/bob/pad/face/database/maskattack.py b/bob/pad/face/database/maskattack.py
index 64bc5bcc98c7f4f2c656f2650079fc67112d41f4..f636c57f5cd5f5fcee0ecd7cef79b37720c47547 100644
--- a/bob/pad/face/database/maskattack.py
+++ b/bob/pad/face/database/maskattack.py
@@ -5,7 +5,7 @@ import os
 import numpy as np
 import bob.io.video
 from bob.bio.video import FrameSelector, FrameContainer
-from bob.pad.face.database import VideoPadFile  
+from bob.pad.face.database import VideoPadFile
 from bob.pad.base.database import PadDatabase
 
 class MaskAttackPadFile(VideoPadFile):
@@ -17,18 +17,18 @@ class MaskAttackPadFile(VideoPadFile):
     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.
-    
+
     """
 
     def __init__(self, f):
         """Init function
-        
+
         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():
@@ -48,10 +48,10 @@ class MaskAttackPadFile(VideoPadFile):
         Parameters
         ----------
         directory : :py:class:`str`
-          String containing the path to the 3DMAD database 
+          String containing the path to the 3DMAD database
           (generated sequences from original data).
         extension : :py:class:`str`
-          Extension of the video files 
+          Extension of the video files
         frame_selector : :py:class:`bob.bio.video.FrameSelector`
             The frame selector to use.
 
@@ -59,7 +59,7 @@ class MaskAttackPadFile(VideoPadFile):
         -------
         video_data : :py:class:`bob.bio.video.utils.FrameContainer`
           video data stored in a FrameContainer
-        
+
         """
         vfilename = self.make_path(directory, extension)
         video = bob.io.video.reader(vfilename)
@@ -69,7 +69,7 @@ class MaskAttackPadFile(VideoPadFile):
 
 class MaskAttackPadDatabase(PadDatabase):
     """High level implementation of the Database class for the 3DMAD database.
-    
+
     Attributes
     ----------
     db : :py:class:`bob.db.maskattack.Database`
@@ -92,12 +92,12 @@ class MaskAttackPadDatabase(PadDatabase):
           The directory where the original data of the database are stored.
         original_extension : :py:class:`str`
           The file name extension of the original data.
-        
+
         """
         from bob.db.maskattack import Database as LowLevelDatabase
         self.db = LowLevelDatabase()
 
-        self.low_level_group_names = ('world', 'dev', 'test')  
+        self.low_level_group_names = ('world', 'dev', 'test')
         self.high_level_group_names = ('train', 'dev', 'eval')
 
         super(MaskAttackPadDatabase, self).__init__(
@@ -147,13 +147,13 @@ class MaskAttackPadDatabase(PadDatabase):
         groups = self.convert_names_to_lowlevel(groups, self.low_level_group_names, self.high_level_group_names)
 
         if groups is not None:
-          
+
           # for training
           lowlevel_purposes = []
           if 'world' in groups and purposes == 'real':
-            lowlevel_purposes.append('trainReal') 
+            lowlevel_purposes.append('trainReal')
           if 'world' in groups and purposes == 'attack':
-            lowlevel_purposes.append('trainMask') 
+            lowlevel_purposes.append('trainMask')
 
           # for dev and eval
           if ('dev' in groups or 'test' in groups) and purposes == 'real':
@@ -163,10 +163,14 @@ class MaskAttackPadDatabase(PadDatabase):
 
         files = self.db.objects(sets=groups, purposes=lowlevel_purposes, **kwargs)
         files = [MaskAttackPadFile(f) for f in files]
+        # set the attributes
+        for f in files:
+          f.original_directory = self.original_directory
+          f.original_extension = self.original_extension
 
         return files
 
-    
+
     def annotations(self, file):
         """Return annotations for a given file object.
 
diff --git a/bob/pad/face/database/msu_mfsd.py b/bob/pad/face/database/msu_mfsd.py
index 4843ff52ea726d9c118273dab11b547b126ee820..59e055a8dad5d6c00e4f43df64cee32ae6856580 100644
--- a/bob/pad/face/database/msu_mfsd.py
+++ b/bob/pad/face/database/msu_mfsd.py
@@ -1,20 +1,14 @@
 #!/usr/bin/env python2
 # -*- coding: utf-8 -*-
 
-#==============================================================================
-# Used in ReplayMobilePadFile class
 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
-
+from bob.extension import rc
 import os
-
 import numpy as np
 
 
-#==============================================================================
 class MsuMfsdPadFile(VideoPadFile):
     """
     A high level implementation of the File class for the MSU MFSD database.
@@ -40,18 +34,20 @@ class MsuMfsdPadFile(VideoPadFile):
         if f.is_real():
             attack_type = None
         else:
-            attack_type = 'attack'
+            attack_type = "attack"
         # attack_type is a string and I decided to make it like this for this
         # particular database. You can do whatever you want for your own database.
 
         super(MsuMfsdPadFile, self).__init__(
-            client_id=f.client_id,
-            path=f.path,
-            attack_type=attack_type,
-            file_id=f.id)
-
-    #==========================================================================
-    def load(self, directory=None, extension=None, frame_selector=FrameSelector(selection_style='all')):
+            client_id=f.client_id, path=f.path, attack_type=attack_type, file_id=f.id
+        )
+
+    def load(
+        self,
+        directory=None,
+        extension=None,
+        frame_selector=FrameSelector(selection_style="all"),
+    ):
         """
         Overridden version of the load method defined in the ``VideoPadFile``.
 
@@ -76,26 +72,27 @@ class MsuMfsdPadFile(VideoPadFile):
             for further details.
         """
 
-        _, extension = os.path.splitext(
-            self.f.videofile())  # get file extension
+        _, extension = os.path.splitext(self.f.videofile())  # get file extension
 
-        video_data_array = self.f.load(
-            directory=directory, extension=extension)
+        video_data_array = self.f.load(directory=directory, extension=extension)
         return frame_selector(video_data_array)
 
 
-#==============================================================================
 class MsuMfsdPadDatabase(PadDatabase):
     """
     A high level implementation of the Database class for the MSU MFSD database.
     """
 
     def __init__(
-            self,
-            protocol='grandtest',  # grandtest is the default protocol for this database
-            original_directory=None,
-            original_extension=None,
-            **kwargs):
+        self,
+        protocol="grandtest",  # grandtest is the default protocol for this database
+        original_directory=None,
+        original_extension=None,
+        annotation_directory=None,
+        annotation_extension='.json',
+        annotation_type='json',
+        **kwargs
+    ):
         """
         **Parameters:**
 
@@ -119,19 +116,24 @@ class MsuMfsdPadDatabase(PadDatabase):
         # 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 = (
-            'train', 'devel',
-            'test')  # group names in the low-level database interface
+            "train",
+            "devel",
+            "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
+            "train",
+            "dev",
+            "eval",
+        )  # names are expected to be like that in objects() function
 
         # Always use super to call parent class methods.
         super(MsuMfsdPadDatabase, self).__init__(
-            name='msu-mfsd',
+            name="msu-mfsd",
             protocol=protocol,
             original_directory=original_directory,
             original_extension=original_extension,
-            **kwargs)
+            **kwargs
+        )
 
     @property
     def original_directory(self):
@@ -141,13 +143,9 @@ class MsuMfsdPadDatabase(PadDatabase):
     def original_directory(self, value):
         self.db.original_directory = value
 
-    #==========================================================================
-    def objects(self,
-                groups=None,
-                protocol=None,
-                purposes=None,
-                model_ids=None,
-                **kwargs):
+    def objects(
+        self, groups=None, protocol=None, purposes=None, model_ids=None, **kwargs
+    ):
         """
         This function returns lists of MsuMfsdPadFile objects, which fulfill the given restrictions.
 
@@ -179,16 +177,21 @@ class MsuMfsdPadDatabase(PadDatabase):
 
         # 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)
+            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.
         files = self.db.objects(group=groups, cls=purposes, **kwargs)
 
         files = [MsuMfsdPadFile(f) for f in files]
+        for f in files:
+            f.original_directory = self.original_directory
+            f.annotation_directory = self.annotation_directory
+            f.annotation_extension = self.annotation_extension
+            f.annotation_type = self.annotation_type
 
         return files
 
-    #==========================================================================
     def annotations(self, f):
         """
         Return annotations for a given file object ``f``, which is an instance
@@ -220,12 +223,14 @@ class MsuMfsdPadDatabase(PadDatabase):
         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]))
+            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
+                "topleft": topleft,
+                "bottomright": bottomright,
             }
 
         return annotations
diff --git a/bob/pad/face/database/replay.py b/bob/pad/face/database/replay.py
index 56004ecffcd6cdc2fe40cdbd823ddb86ba15e558..1d3803e78d3d48ac29c47e215b64be4cfe692835 100644
--- a/bob/pad/face/database/replay.py
+++ b/bob/pad/face/database/replay.py
@@ -1,11 +1,13 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-# Used in ReplayMobilePadFile class
-from bob.pad.base.database import PadDatabase
+from bob.pad.base.database import PadDatabase  # Used in ReplayMobilePadFile class
 from bob.pad.face.database import VideoPadFile  # Used in ReplayPadFile class
-from bob.pad.face.utils import frames, number_of_frames
 from bob.extension import rc
+from bob.ip.facedetect import expected_eye_positions, BoundingBox
+from bob.db.base.annotations import read_annotation_file
+
+REPLAY_ATTACK_FRAME_SHAPE = (3, 240, 320)
 
 
 class ReplayPadFile(VideoPadFile):
@@ -35,16 +37,73 @@ class ReplayPadFile(VideoPadFile):
         if f.is_real():
             attack_type = None
         else:
-            attack_type = 'attack'
+            attack_type = "attack"
         # attack_type is a string and I decided to make it like this for this
         # particular database. You can do whatever you want for your own
         # database.
 
         super(ReplayPadFile, self).__init__(
-            client_id=f.client_id,
-            path=f.path,
-            attack_type=attack_type,
-            file_id=f.id)
+            client_id=f.client_id, path=f.path, attack_type=attack_type, file_id=f.id
+        )
+
+    @property
+    def frame_shape(self):
+        """Returns the size of each frame in this database.
+
+        Returns
+        -------
+        (int, int, int)
+            The (#Channels, Height, Width) which is (3, 240, 320).
+        """
+        return REPLAY_ATTACK_FRAME_SHAPE
+
+    @property
+    def annotations(self):
+        """
+        Return annotations as a dictionary of dictionaries.
+
+        If the file object has an attribute of annotation_directory, it will read
+        annotations from there instead of loading annotations that are shipped with the
+        database.
+
+        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.
+        """
+        if (
+            hasattr(self, "annotation_directory")
+            and self.annotation_directory is not None
+        ):
+            path = self.make_path(self.annotation_directory, extension=".json")
+            return read_annotation_file(path, annotation_type="json")
+
+        # 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
+        annots = self.f.bbx(directory=self.original_directory)
+
+        annotations = {}  # dictionary to return
+
+        for fn, frame_annots in enumerate(annots):
+
+            topleft = (frame_annots[2], frame_annots[1])
+            bottomright = (
+                frame_annots[2] + frame_annots[4],
+                frame_annots[1] + frame_annots[3],
+            )
+            annotations[str(fn)] = {"topleft": topleft, "bottomright": bottomright}
+
+            size = (bottomright[0] - topleft[0], bottomright[1] - topleft[1])
+            bounding_box = BoundingBox(topleft, size)
+            annotations[str(fn)].update(expected_eye_positions(bounding_box))
+
+        return annotations
 
 
 class ReplayPadDatabase(PadDatabase):
@@ -54,12 +113,14 @@ class ReplayPadDatabase(PadDatabase):
     """
 
     def __init__(
-            self,
-            # grandtest is the default protocol for this database
-            protocol='grandtest',
-            original_directory=rc['bob.db.replay.directory'],
-            original_extension='.mov',
-            **kwargs):
+        self,
+        # grandtest is the default protocol for this database
+        protocol="grandtest",
+        original_directory=rc["bob.db.replay.directory"],
+        original_extension=".mov",
+        annotation_directory=None,
+        **kwargs
+    ):
         """
         Parameters
         ----------
@@ -86,19 +147,25 @@ class ReplayPadDatabase(PadDatabase):
         # 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 = (
-            'train', 'devel',
-            'test')  # group names in the low-level database interface
+            "train",
+            "devel",
+            "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
+            "train",
+            "dev",
+            "eval",
+        )  # names are expected to be like that in objects() function
 
         # Always use super to call parent class methods.
         super(ReplayPadDatabase, self).__init__(
-            name='replay',
+            name="replay",
             protocol=protocol,
             original_directory=original_directory,
             original_extension=original_extension,
-            **kwargs)
+            annotation_directory=annotation_directory,
+            **kwargs
+        )
 
     @property
     def original_directory(self):
@@ -108,12 +175,9 @@ class ReplayPadDatabase(PadDatabase):
     def original_directory(self, value):
         self.db.original_directory = value
 
-    def objects(self,
-                groups=None,
-                protocol=None,
-                purposes=None,
-                model_ids=None,
-                **kwargs):
+    def objects(
+        self, groups=None, protocol=None, purposes=None, model_ids=None, **kwargs
+    ):
         """
         This function returns lists of ReplayPadFile objects, which fulfill the
         given restrictions.
@@ -146,12 +210,19 @@ class ReplayPadDatabase(PadDatabase):
 
         # 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)
+            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.
         files = self.db.objects(
-            protocol=protocol, groups=groups, cls=purposes, **kwargs)
+            protocol=protocol, groups=groups, cls=purposes, **kwargs
+        )
         files = [ReplayPadFile(f) for f in files]
+        # set the attributes
+        for f in files:
+            f.original_directory = self.original_directory
+            f.original_extension = self.original_extension
+            f.annotation_directory = self.annotation_directory
         return files
 
     def annotations(self, f):
@@ -178,26 +249,7 @@ class ReplayPadDatabase(PadDatabase):
             is the dictionary defining the coordinates of the face bounding box
             in frame N.
         """
-
-        # 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
-        annots = f.f.bbx(directory=self.original_directory)
-
-        annotations = {}  # dictionary to return
-
-        for fn, frame_annots in enumerate(annots):
-
-            topleft = (frame_annots[2], frame_annots[1])
-            bottomright = (frame_annots[2] + frame_annots[4],
-                           frame_annots[1] + frame_annots[3])
-
-            annotations[str(fn)] = {
-                'topleft': topleft,
-                'bottomright': bottomright
-            }
-
-        return annotations
+        return f.annotations
 
     def frames(self, padfile):
         """Yields the frames of the padfile one by one.
@@ -212,11 +264,7 @@ class ReplayPadDatabase(PadDatabase):
         :any:`numpy.array`
             A frame of the video. The size is (3, 240, 320).
         """
-        vfilename = padfile.make_path(
-            directory=self.original_directory,
-            extension=self.original_extension)
-        for retval in frames(vfilename):
-            yield retval
+        return padfile.frames
 
     def number_of_frames(self, padfile):
         """Returns the number of frames in a video file.
@@ -231,10 +279,7 @@ class ReplayPadDatabase(PadDatabase):
         int
             The number of frames.
         """
-        vfilename = padfile.make_path(
-            directory=self.original_directory,
-            extension=self.original_extension)
-        return number_of_frames(vfilename)
+        return padfile.number_of_frames
 
     @property
     def frame_shape(self):
@@ -245,4 +290,4 @@ class ReplayPadDatabase(PadDatabase):
         (int, int, int)
             The (#Channels, Height, Width) which is (3, 240, 320).
         """
-        return (3, 240, 320)
+        return REPLAY_ATTACK_FRAME_SHAPE
diff --git a/bob/pad/face/database/replay_mobile.py b/bob/pad/face/database/replay_mobile.py
index 8e2547c37256d04d11603ca36ea0ca5b7ab2f942..b7fe209db2c1e3f2feb4a25ec1e03ab01063b1e9 100644
--- a/bob/pad/face/database/replay_mobile.py
+++ b/bob/pad/face/database/replay_mobile.py
@@ -5,48 +5,13 @@
 from bob.bio.video import FrameSelector
 from bob.pad.base.database import PadDatabase
 from bob.pad.face.database import VideoPadFile
-from bob.pad.face.utils import frames, number_of_frames
+from bob.pad.face.utils import number_of_frames
 from bob.db.base.annotations import read_annotation_file
-import numpy
 from bob.extension import rc
 
 REPLAYMOBILE_FRAME_SHAPE = (3, 1280, 720)
 
 
-def replaymobile_annotations(lowlevelfile, 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
-    annots = lowlevelfile.bbx(directory=original_directory)
-
-    annotations = {}  # dictionary to return
-
-    for fn, frame_annots in enumerate(annots):
-
-        topleft = (frame_annots[1], frame_annots[0])
-        bottomright = (frame_annots[1] + frame_annots[3],
-                       frame_annots[0] + frame_annots[2])
-
-        annotations[str(fn)] = {
-            'topleft': topleft,
-            'bottomright': bottomright
-        }
-
-    return annotations
-
-
-def replaymobile_frames(lowlevelfile, original_directory):
-    vfilename = lowlevelfile.make_path(
-        directory=original_directory,
-        extension='.mov')
-    is_not_tablet = not lowlevelfile.is_tablet()
-    for frame in frames(vfilename):
-        frame = numpy.rollaxis(frame, 2, 1)
-        if is_not_tablet:
-            frame = frame[:, ::-1, :]
-        yield frame
-
-
 class ReplayMobilePadFile(VideoPadFile):
     """
     A high level implementation of the File class for the Replay-Mobile
@@ -116,6 +81,7 @@ class ReplayMobilePadFile(VideoPadFile):
 
     @property
     def annotations(self):
+        from bob.db.replaymobile.models import replaymobile_annotations
         if self.annotation_directory is not None:
             # return the external annotations
             annotations = read_annotation_file(
@@ -129,6 +95,7 @@ class ReplayMobilePadFile(VideoPadFile):
 
     @property
     def frames(self):
+        from bob.db.replaymobile.models import replaymobile_frames
         return replaymobile_frames(self.f, self.original_directory)
 
     @property
diff --git a/bob/pad/face/test/test_databases.py b/bob/pad/face/test/test_databases.py
index 594899a638264d15d881d95877a1735eeabe316e..0dd94cdc2cca4f5b77c80a44d1d3702430c16c51 100644
--- a/bob/pad/face/test/test_databases.py
+++ b/bob/pad/face/test/test_databases.py
@@ -224,7 +224,7 @@ def test_casiasurf():
         assert len(casiasurf.objects(groups=('dev',), purposes=('real',))) == 2994
         assert len(casiasurf.objects(groups=('dev',), purposes=('attack',))) == 6614
         assert len(casiasurf.objects(groups=('dev',), purposes=('real','attack'))) == 9608
-        assert len(casiasurf.objects(groups=('eval',), purposes=('real',))) == 17458 
+        assert len(casiasurf.objects(groups=('eval',), purposes=('real',))) == 17458
         assert len(casiasurf.objects(groups=('eval',), purposes=('attack',))) == 40252
         assert len(casiasurf.objects(groups=('eval',), purposes=('real','attack'))) == 57710
 
@@ -270,8 +270,10 @@ def test_casia_fasd():
     # test annotations since they are shipped with bob.db.casia_fasd
     f = [f for f in casia_fasd.objects() if f.path == 'train_release/1/2'][0]
     assert len(f.annotations) == 132
-    assert f.annotations['0'] == \
-        {'topleft': (102, 214), 'bottomright': (242, 354)}
+    a = f.annotations['0']
+    oracle = {'topleft': (102, 214), 'bottomright': (242, 354),
+              'reye': (151.0, 249.0), 'leye': (151.0, 319.0)}
+    assert a == oracle, a
 
 
 @db_available('casia_fasd')