diff --git a/bob/pad/face/config/database/replay_mobile.py b/bob/pad/face/config/database/replay_mobile.py new file mode 100644 index 0000000000000000000000000000000000000000..000e93a169f0029c2c2a5da03d5b23d453ca5b4d --- /dev/null +++ b/bob/pad/face/config/database/replay_mobile.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +"""`Replay-Mobile`_ is a database for face PAD experiments. + +The Replay-Mobile Database for face spoofing consists of 1030 video clips of photo and video attack attempts to 40 clients, +under different lighting conditions. +These videos were recorded with current devices from the market -- an iPad Mini2 (running iOS) and a LG-G4 smartphone (running Android). +This Database was produced at the Idiap Research Institute (Switzerland) within the framework +of collaboration with Galician Research and Development Center in Advanced Telecommunications - Gradiant (Spain). +The reference citation is [CBVM16]_. + +You can download the raw data of the `Replay-Mobile`_ database by following +the link. + +.. include:: links.rst +""" + +from bob.pad.face.database import ReplayMobilePadDatabase + + +# 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_REPLAY_MOBILE_DIRECTORY]" +"""Value of ``~/.bob_bio_databases.txt`` for this database""" + +original_extension = ".mov" # extension of the data files + + +database = ReplayMobilePadDatabase( + 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 Replay-Mobile +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_REPLAY_MOBILE_DIRECTORY]``. +You must make sure to create ``${HOME}/.bob_bio_databases.txt`` setting this +value to the place where you actually installed the Replay-Mobile Database, as +explained in the section :ref:`bob.pad.face.baselines`. +""" \ No newline at end of file diff --git a/bob/pad/face/database/__init__.py b/bob/pad/face/database/__init__.py index 5d92e76c278fc31d6f36fa814b6ebe62643787cd..7b20112adff6a563706c7f182ec773726816ccd0 100644 --- a/bob/pad/face/database/__init__.py +++ b/bob/pad/face/database/__init__.py @@ -1,4 +1,5 @@ from .replay import ReplayPadDatabase +from .replay_mobile import ReplayMobilePadDatabase # gets sphinx autodoc done right - don't remove it def __appropriate__(*args): @@ -16,6 +17,7 @@ def __appropriate__(*args): __appropriate__( ReplayPadDatabase, + ReplayMobilePadDatabase, ) __all__ = [_ for _ in dir() if not _.startswith('_')] diff --git a/bob/pad/face/database/replay.py b/bob/pad/face/database/replay.py index fd8bb048677703d1675c44eedce1d732c8161cdf..1d1a663548a40e4751f6564e68d55779fd816d41 100644 --- a/bob/pad/face/database/replay.py +++ b/bob/pad/face/database/replay.py @@ -10,6 +10,7 @@ from bob.pad.base.database import PadFile # Used in ReplayPadFile class from bob.pad.base.database import PadDatabase + #============================================================================== class ReplayPadFile(PadFile): @@ -44,11 +45,11 @@ class ReplayPadFile(PadFile): super(ReplayPadFile, 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 ``bob.db.base.File``. + Overridden version of the load method defined in the ``PadFile``. **Parameters:** @@ -73,8 +74,8 @@ class ReplayPadFile(PadFile): return video_data # video data -#============================================================================== +#============================================================================== class ReplayPadDatabase(PadDatabase): """ A high level implementation of the Database class for the REPLAY-ATTACK database. @@ -119,8 +120,8 @@ class ReplayPadDatabase(PadDatabase): 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. @@ -158,8 +159,8 @@ class ReplayPadDatabase(PadDatabase): files = [ReplayPadFile(f) for f in files] return files - #========================================================================== + #========================================================================== def annotations(self, f): """ Return annotations for a given file object ``f``, which is an instance diff --git a/bob/pad/face/database/replay_mobile.py b/bob/pad/face/database/replay_mobile.py new file mode 100644 index 0000000000000000000000000000000000000000..773ca43d469e5341500948c12c2837faa61aa0ea --- /dev/null +++ b/bob/pad/face/database/replay_mobile.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + + +#============================================================================== +import bob.bio.video # Used in ReplayMobilePadFile class + +from bob.pad.base.database import PadFile # Used in ReplayMobilePadFile class + +from bob.pad.base.database import PadDatabase + + +#============================================================================== +class ReplayMobilePadFile(PadFile): + """ + A high level implementation of the File class for the Replay-Mobile 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 Replay-Mobile database, in the bob.db.replaymobile.models.py file. + """ + + self.f = f + # this f is actually an instance of the File class that is defined in + # bob.db.replaymobile.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(ReplayMobilePadFile, self).__init__(client_id=f.client_id, path=f.path, + attack_type=attack_type, file_id=f.id) + + + #========================================================================== + def convert_arr_to_frame_cont(self, data): + """ + This function converts an input 4D array with frames into FrameContainer, + where each frame is an RGB image. The dimensionality of the input array + is [N_frames, 3, N_rows, N_cols]. + + **Parameters:** + + ``data`` : 4D :py:class:`numpy.ndarray` + An input 4D array with frames of the dimensionality: + [N_frames, 3, N_rows, N_cols]. + + **Returns:** + + ``frames`` : FrameContainer + Resulting FrameContainer containing RGB frames. + """ + + frames = bob.bio.video.FrameContainer() # initialize the FrameContainer + + for idx, sample in enumerate(data): + + frames.add(idx, sample) + + return frames + + + #========================================================================== + 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 path to the Replay-Mobile database. + + ``extension`` : :py:class:`str` + Extension of the video files in the Replay-Mobile database. + + **Returns:** + + ``video_data`` : FrameContainer + Video data stored in the FrameContainer, see ``bob.bio.video.utils.FrameContainer`` + for further details. + """ + + video_data_array = self.f.load(directory = directory, + extension = extension) + + video_data = self.convert_arr_to_frame_cont(video_data_array) # the result is now a FrameContainer + + return video_data + + +#============================================================================== +class ReplayMobilePadDatabase(PadDatabase): + """ + A high level implementation of the Database class for the Replay-Mobile database. + """ + + 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. + + ``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.replaymobile 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 = ('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(ReplayMobilePadDatabase, self).__init__( + name = 'replay-mobile', + 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 ReplayMobilePadFile 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`` : :py:class:`str` + A list of ReplayMobilePadFile 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. + files = self.db.objects(protocol=protocol, groups=groups, cls=purposes, **kwargs) + + files = [ReplayMobilePadFile(f) for f in files] + + return files + + + #========================================================================== + def annotations(self, f): + """ + Return annotations for a given file object ``f``, which is an instance + of ``ReplayMobilePadFile`` defined in the HLDI of the Replay-Mobile DB. + The ``load()`` method of ``ReplayMobilePadFile`` 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 ``ReplayMobilePadFile`` 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. + """ + + 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 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 + diff --git a/bob/pad/face/test/test_databases.py b/bob/pad/face/test/test_databases.py index 19f956926bcbcda45ebd4127909967252404201a..68a61e4e0762b59be1d2b5441a0bfa66437faf8a 100644 --- a/bob/pad/face/test/test_databases.py +++ b/bob/pad/face/test/test_databases.py @@ -22,3 +22,20 @@ def test_replay(): except IOError as e: raise SkipTest( "The database could not be queried; probably the db.sql3 file is missing. Here is the error: '%s'" % e) + + +@db_available('replaymobile') +def test_replaymobile(): + replaymobile = bob.bio.base.load_resource('replay-mobile', 'database', preferred_package='bob.pad.face', package_prefix='bob.pad.') + try: + + assert len( replaymobile.objects(groups=['train', 'dev', 'eval']) )== 1030 + assert len( replaymobile.objects(groups=['train', 'dev']) ) == 728 + assert len( replaymobile.objects(groups=['train']) ) == 312 + assert len( replaymobile.objects(groups=['train', 'dev', 'eval'], protocol = 'grandtest') )== 1030 + assert len( replaymobile.objects(groups=['train', 'dev', 'eval'], protocol = 'grandtest', purposes='real') ) == 390 + assert len( replaymobile.objects(groups=['train', 'dev', 'eval'], protocol = 'grandtest', purposes='attack') ) == 640 + + except IOError as e: + raise SkipTest( + "The database could not be queried; probably the db.sql3 file is missing. Here is the error: '%s'" % e) \ No newline at end of file diff --git a/doc/links.rst b/doc/links.rst index 56b4ead8c53eb5600ebee52aae0fed9e71f0354a..32f077c3900fd70ccb8468758ef3f3773c01d510 100644 --- a/doc/links.rst +++ b/doc/links.rst @@ -10,5 +10,6 @@ .. _installation: https://www.idiap.ch/software/bob/install .. _bob.pad.base: https://pypi.python.org/pypi/bob.pad.base .. _replayattack: https://www.idiap.ch/dataset/replayattack +.. _replay-mobile: https://www.idiap.ch/dataset/replay-mobile .. _dependencies: https://gitlab.idiap.ch/bob/bob/wikis/Dependencies diff --git a/doc/resources.rst b/doc/resources.rst index f943abba35447a02e73dca63e70b6be90bfce79a..b761a780b49247a1665bcaf187f0b56cf5e9c82e 100644 --- a/doc/resources.rst +++ b/doc/resources.rst @@ -24,12 +24,22 @@ These configuration files/resources contain entry points for the ``--database`` .. _bob.pad.face.resources.databases.replay: -Replay-attack Database +Replay-Attack Database ================================================================================ .. automodule:: bob.pad.face.config.database.replay :members: + +.. _bob.pad.face.resources.databases.replay_mobile: + +Replay-Mobile Database +================================================================================ + +.. automodule:: bob.pad.face.config.database.replay_mobile + :members: + + --------------------------------- diff --git a/setup.py b/setup.py index 13ada3f681ca3b229b4a059ed700f8b52a976cd6..27af5799b31136418c062267cff5970c76e52574 100644 --- a/setup.py +++ b/setup.py @@ -96,6 +96,7 @@ setup( # registered databases: 'bob.pad.database': [ 'replay = bob.pad.face.config.database.replay:database', + 'replay-mobile = bob.pad.face.config.database.replay_mobile:database', ], # registered configurations: diff --git a/test-requirements.txt b/test-requirements.txt index 0b585f15407adc7db098f6c64c870b2bc7ae47c4..6a55b2f913576b9a6cd1ae96aec700e3d7931a2e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,2 @@ bob.db.replay +bob.db.replaymobile