diff --git a/bob/pad/face/config/database/aggregated_db.py b/bob/pad/face/config/database/aggregated_db.py new file mode 100644 index 0000000000000000000000000000000000000000..375a1751a9346676bda178e95800ac63b508e1f9 --- /dev/null +++ b/bob/pad/face/config/database/aggregated_db.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +"""Aggregated Db is a database for face PAD experiments. + +This database aggregates the data from 3 publicly available data-sets: +`REPLAYATTACK`_, `REPLAY-MOBILE`_ and `MSU MFSD`_. + +You can download the data for the above databases by following the corresponding +links. + +The reference citation for the `REPLAYATTACK`_ is [CAM12]_. +The reference citation for the `REPLAY-MOBILE`_ is [CBVM16]_. +The reference citation for the `MSU MFSD`_ is [WHJ15]_. + +.. include:: links.rst +""" + +from bob.pad.face.database import AggregatedDbPadDatabase + +# Directory where the data files are stored. +# This directory is given in the .bob_bio_databases.txt file located in your home directory +original_directory = "[YOUR_AGGREGATED_DB_DIRECTORIES]" +"""Value of ``~/.bob_bio_databases.txt`` for this database""" + +original_extension = ".mov" # extension of the data files + +database = AggregatedDbPadDatabase( + protocol='grandtest', + original_directory=original_directory, + original_extension=original_extension, + training_depends_on_protocol=True, +) +"""The :py:class:`bob.pad.base.database.PadDatabase` derivative with Aggregated Db +database settings. + +.. warning:: + + This class only provides a programmatic interface to load data in an orderly + manner, respecting usage protocols. It does **not** contain the raw + data files. You should procure those yourself. + +Notice that ``original_directory`` is set to ``[YOUR_AGGREGATED_DB_DIRECTORIES]``. +You must make sure to create ``${HOME}/.bob_bio_databases.txt`` file setting this +value to the places where you actually installed the Replay-Attack, Replay-Mobile +and MSU MFSD Databases. In particular, the paths pointing to these 3 databases +must be saparated with a space. For example: +[YOUR_AGGREGATED_DB_DIRECTORIES] = <PATH_TO_REPLAY_ATTACK> <PATH_TO_REPLAY_MOBILE> + <PATH_TO_MSU_MFSD> +""" \ No newline at end of file diff --git a/bob/pad/face/database/__init__.py b/bob/pad/face/database/__init__.py index abf2f69122cb549489bf3f043f6c66e8c160ed88..bc586be28417c221340e0bf11297282f5cb4e6d6 100644 --- a/bob/pad/face/database/__init__.py +++ b/bob/pad/face/database/__init__.py @@ -1,6 +1,8 @@ from .replay import ReplayPadDatabase from .replay_mobile import ReplayMobilePadDatabase from .msu_mfsd import MsuMfsdPadDatabase +from .aggregated_db import AggregatedDbPadDatabase + # gets sphinx autodoc done right - don't remove it def __appropriate__(*args): @@ -20,6 +22,7 @@ __appropriate__( ReplayPadDatabase, ReplayMobilePadDatabase, MsuMfsdPadDatabase, + AggregatedDbPadDatabase, ) __all__ = [_ for _ in dir() if not _.startswith('_')] diff --git a/bob/pad/face/database/aggregated_db.py b/bob/pad/face/database/aggregated_db.py new file mode 100644 index 0000000000000000000000000000000000000000..8decbe0e640225f6eb258ba8bc720b7525de9bc1 --- /dev/null +++ b/bob/pad/face/database/aggregated_db.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +#============================================================================== +from bob.pad.base.database import PadFile # Used in ReplayPadFile class + +from bob.pad.base.database import PadDatabase + +# Import HLDI for the databases to aggregate: +from bob.pad.face.database import replay as replay_hldi + +from bob.pad.face.database import replay_mobile as replay_mobile_hldi + +from bob.pad.face.database import msu_mfsd as msu_mfsd_hldi + +#============================================================================== +class AggregatedDbPadFile(PadFile): + """ + A high level implementation of the File class for the Aggregated Database + uniting 3 databases: REPLAY-ATTACK, REPLAY-MOBILE and MSU MFSD. + """ + + def __init__(self, f): + """ + **Parameters:** + + ``f`` : :py:class:`object` + An instance of the File class defined in the low level db interface + of the Replay-Attack or Replay-Mobile or MSU MFSD database, + in the bob.db.replay.models.py file or + in the bob.db.replaymobile.models.py file or + in the bob.db.msu_mfsd_mod.models.py file. + """ + + self.f = f + # this f is actually an instance of the File class that is defined in + # bob.db.<database_name>.models and the PadFile class here needs + # client_id, path, attack_type, file_id for initialization. We have to + # convert information here and provide them to PadFile. attack_type is a + # little tricky to get here. Based on the documentation of PadFile: + # In cased of a spoofed data, this parameter should indicate what kind of spoofed attack it is. + # The default None value is interpreted that the PadFile is a genuine or real sample. + if f.is_real(): + attack_type = None + else: + 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(AggregatedDbPadFile, self).__init__(client_id=f.client_id, path=f.path, + attack_type=attack_type, file_id=f.id) + + + #========================================================================== + def load(self, directory=None, extension='.mov'): + """ + Overridden version of the load method defined in the ``PadFile``. + + **Parameters:** + + ``directory`` : :py:class:`str` + String containing the paths to all databases used in this aggregated + database. The paths are separated with a space. + + ``extension`` : :py:class:`str` + Extension of the video files in the REPLAY-ATTACK and REPLAY-MOBILE + databases. The extension of files in MSU MFSD is not taken into account + in the HighLevel DB Interface of MSU MFSD. Default: '.mov'. + + **Returns:** + + ``video_data`` : FrameContainer + Video data stored in the FrameContainer, see ``bob.bio.video.utils.FrameContainer`` + for further details. + """ + + import bob.db.replay + import bob.db.replaymobile + import bob.db.msu_mfsd_mod + + directories = directory.split(" ") + + if isinstance(self.f, bob.db.replay.models.File): # check if instance of File class of LLDI of Replay-Attack + + db_pad_file = replay_hldi.ReplayPadFile(self.f) # replay_hldi is HLDI of Replay-Attack + + directory = directories[0] + + if isinstance(self.f, bob.db.replaymobile.models.File): # check if instance of File class of LLDI of Replay-Mobile + + db_pad_file = replay_mobile_hldi.ReplayMobilePadFile(self.f) # replay_mobile_hldi is HLDI of Replay-Mobile + + directory = directories[1] + + if isinstance(self.f, bob.db.msu_mfsd_mod.models.File): # check if instance of File class of LLDI of MSU MFSD + + db_pad_file = msu_mfsd_hldi.MsuMfsdPadFile(self.f) # msu_mfsd_hldi is HLDI of MSU MFSD + + directory = directories[2] + + video_data = db_pad_file.load(directory = directory, extension = extension) + + return video_data # video data + + +#============================================================================== +class AggregatedDbPadDatabase(PadDatabase): + """ + A high level implementation of the Database class for the Aggregated Database + uniting 3 databases: REPLAY-ATTACK, REPLAY-MOBILE and MSU MFSD. + """ + + def __init__( + self, + protocol='grandtest', # grandtest is the default protocol for this database + original_directory=None, + original_extension=None, + **kwargs): + """ + **Parameters:** + + ``protocol`` : :py:class:`str` or ``None`` + The name of the protocol that defines the default experimental setup + for this database. Default: 'grandtest'. + + ``original_directory`` : :py:class:`str` + String containing the paths to all databases used in this aggregated + database. The paths are separated with a space. Default: None. + + ``original_extension`` : :py:class:`str` + Extension of the video files in the REPLAY-ATTACK and REPLAY-MOBILE + databases. The extension of files in MSU MFSD is not taken into account + in the HighLevel DB Interface of MSU MFSD. Default: None. + + ``kwargs`` + The arguments of the :py:class:`bob.bio.base.database.BioDatabase` base class constructor. + """ + + # Import LLDI for all databases: + import bob.db.replay + import bob.db.replaymobile + import bob.db.msu_mfsd_mod + + self.replay_db = bob.db.replay.Database() + self.replaymobile_db = bob.db.replaymobile.Database() + self.msu_mfsd_db = bob.db.msu_mfsd_mod.Database() + + # 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 + 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(AggregatedDbPadDatabase, self).__init__( + name = 'aggregated_db', + protocol = protocol, + original_directory = original_directory, + original_extension = original_extension, + **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. + + 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. + The protocol is dependent on your database. + If you do not have protocols defined, just ignore this field. + + ``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`` : [AggregatedDbPadFile] + A list of AggregatedDbPadFile 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. + replay_files = self.replay_db.objects(protocol=protocol, groups=groups, cls=purposes, **kwargs) + + replaymobile_files = self.replaymobile_db.objects(protocol=protocol, groups=groups, cls=purposes, **kwargs) + + msu_mfsd_files = self.msu_mfsd_db.objects(group=groups, cls=purposes, **kwargs) + + files = replay_files + replaymobile_files + msu_mfsd_files # append all files to a single list + + files = [AggregatedDbPadFile(f) for f in files] + return files + + + #========================================================================== + def annotations(self, f): + """ + Return annotations for a given file object ``f``, which is an instance + of ``AggregatedDbPadFile`` defined in the HLDI of the Aggregated DB. + The ``load()`` method of ``AggregatedDbPadFile`` 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 ``AggregatedDbPadFile`` 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. + """ + + import bob.db.replay + import bob.db.replaymobile + import bob.db.msu_mfsd_mod + + directories = self.original_directory.split(" ") + + if isinstance(f.f, bob.db.replay.models.File): # check if instance of File class of LLDI of Replay-Attack + + hldi_db = replay_hldi.ReplayPadDatabase(original_directory = directories[0]) + + if isinstance(f.f, bob.db.replaymobile.models.File): # check if instance of File class of LLDI of Replay-Mobile + + hldi_db = replay_mobile_hldi.ReplayMobilePadDatabase(original_directory = directories[1]) + + if isinstance(f.f, bob.db.msu_mfsd_mod.models.File): # check if instance of File class of LLDI of MSU MFSD + + hldi_db = msu_mfsd_hldi.MsuMfsdPadDatabase(original_directory = directories[2]) + + annotations = hldi_db.annotations(f) + + return annotations + + + + + + + + + + + + + + diff --git a/bob/pad/face/database/msu_mfsd.py b/bob/pad/face/database/msu_mfsd.py index 7b183441c2128e222c17725268c6e117328ef8dc..52c7fb8fca9e2ed4594ed465f9d8cba2d353bbd5 100644 --- a/bob/pad/face/database/msu_mfsd.py +++ b/bob/pad/face/database/msu_mfsd.py @@ -181,7 +181,7 @@ class MsuMfsdPadDatabase(PadDatabase): **Returns:** - ``files`` : :py:class:`str` + ``files`` : [MsuMfsdPadFile] A list of MsuMfsdPadFile objects. """ diff --git a/bob/pad/face/database/replay.py b/bob/pad/face/database/replay.py index 1d1a663548a40e4751f6564e68d55779fd816d41..d9656f3dd0639a73fcf698b1944a2b9687eae252 100644 --- a/bob/pad/face/database/replay.py +++ b/bob/pad/face/database/replay.py @@ -148,9 +148,10 @@ class ReplayPadDatabase(PadDatabase): **Returns:** - ``files`` : :py:class:`str` + ``files`` : [ReplayPadFile] A list of ReplayPadFile 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 diff --git a/bob/pad/face/database/replay_mobile.py b/bob/pad/face/database/replay_mobile.py index 773ca43d469e5341500948c12c2837faa61aa0ea..9745e181a9ef6090f0d9c38f0af8d42208bf9bdd 100644 --- a/bob/pad/face/database/replay_mobile.py +++ b/bob/pad/face/database/replay_mobile.py @@ -173,7 +173,7 @@ class ReplayMobilePadDatabase(PadDatabase): **Returns:** - ``files`` : :py:class:`str` + ``files`` : [ReplayMobilePadFile] A list of ReplayMobilePadFile objects. """