diff --git a/bob/bio/face/config/database/gbu.py b/bob/bio/face/config/database/gbu.py deleted file mode 100644 index 31053e6f36830bcb75a0ecbab40d77cfeb6e946e..0000000000000000000000000000000000000000 --- a/bob/bio/face/config/database/gbu.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python - - -from bob.bio.base.pipelines.vanilla_biometrics import DatabaseConnector -from bob.extension import rc -from bob.bio.face.database import GBUBioDatabase - - -mbgc_v1_directory = rc["bob.db.gbu.directory"] - -database = DatabaseConnector( - GBUBioDatabase( - original_directory=mbgc_v1_directory, - protocol="Good", - models_depend_on_protocol=True, - all_files_options={"subworld": "x2"}, - extractor_training_options={"subworld": "x2"}, - projector_training_options={"subworld": "x2"}, - enroller_training_options={"subworld": "x2"}, - ) -) diff --git a/bob/bio/face/config/database/gbu_bad.py b/bob/bio/face/config/database/gbu_bad.py new file mode 100644 index 0000000000000000000000000000000000000000..06354486d2e4336c7cdba99b257d0b02e514db1d --- /dev/null +++ b/bob/bio/face/config/database/gbu_bad.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + + +from bob.extension import rc +from bob.bio.face.database import GBUDatabase + + +mbgc_v1_directory = rc["bob.bio.face.gbu.directory"] + +database = GBUDatabase(protocol="Bad") + diff --git a/bob/bio/face/config/database/gbu_good.py b/bob/bio/face/config/database/gbu_good.py new file mode 100644 index 0000000000000000000000000000000000000000..521bb9e9c453238fca3223cbe34cd63210b6a746 --- /dev/null +++ b/bob/bio/face/config/database/gbu_good.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + + +from bob.extension import rc +from bob.bio.face.database import GBUDatabase + + +mbgc_v1_directory = rc["bob.bio.face.gbu.directory"] + +database = GBUDatabase(protocol="Good") + diff --git a/bob/bio/face/config/database/gbu_ugly.py b/bob/bio/face/config/database/gbu_ugly.py new file mode 100644 index 0000000000000000000000000000000000000000..8c271788e53b8098a1065d2019a42dc95ea084c3 --- /dev/null +++ b/bob/bio/face/config/database/gbu_ugly.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + + +from bob.extension import rc +from bob.bio.face.database import GBUDatabase + + +mbgc_v1_directory = rc["bob.bio.face.gbu.directory"] + +database = GBUDatabase(protocol="Ugly") + diff --git a/bob/bio/face/database/__init__.py b/bob/bio/face/database/__init__.py index 460ad8c804a3f6ea700604ae1b6b11bdf1c02ed4..2bcc87a10f40ce3cde46de6fd07b13a30bf6ca57 100644 --- a/bob/bio/face/database/__init__.py +++ b/bob/bio/face/database/__init__.py @@ -5,7 +5,7 @@ from .database import FaceBioFile from .mobio import MobioDatabase from .replay import ReplayBioDatabase from .atnt import AtntBioDatabase -from .gbu import GBUBioDatabase +from .gbu import GBUDatabase from .arface import ARFaceBioDatabase from .lfw import LFWBioDatabase from .multipie import MultipieDatabase @@ -43,7 +43,7 @@ __appropriate__( MobioDatabase, ReplayBioDatabase, AtntBioDatabase, - GBUBioDatabase, + GBUDatabase, ARFaceBioDatabase, LFWBioDatabase, MultipieDatabase, diff --git a/bob/bio/face/database/gbu.py b/bob/bio/face/database/gbu.py index af1226d930336df245de5fb1af32356790883344..bfb0b5e37bb5d432c78f67d8409d759bc7972ecc 100644 --- a/bob/bio/face/database/gbu.py +++ b/bob/bio/face/database/gbu.py @@ -3,60 +3,278 @@ # Tiago de Freitas Pereira <tiago.pereira@idiap.ch> # Sat 20 Aug 15:43:10 CEST 2016 +from bob.bio.base.pipelines.vanilla_biometrics.abstract_classes import Database +from bob.extension.download import search_file +from bob.pipelines import DelayedSample, SampleSet +import xml.sax +import os +from functools import partial +from bob.extension import rc +import bob.io.base + +from bob.extension.download import get_file + """ - GBU database implementation of bob.bio.base.database.ZTDatabase interface. - It is an extension of an SQL-based database interface, which directly talks to GBU database, for - verification experiments (good to use in bob.bio.base framework). +GBU Database + +Several of the rules used in this code were imported from +https://gitlab.idiap.ch/bob/bob.db.gbu/-/blob/master/bob/db/gbu/create.py """ -from .database import FaceBioFile -from bob.bio.base.database import BioDatabase +def load_annotations(annotations_file): + annotations = dict() + for i, line in enumerate(annotations_file.readlines()): + # Skip the first line + if i == 0: + continue + line = line.split(",") + path = os.path.splitext(os.path.basename(line[0]))[0] + annotations[path] = { + "leye": (float(line[-1]), float(line[-2])), + "reye": (float(line[2]), float(line[1])), + } + return annotations + + +class File(object): + def __init__(self, subject_id, reference_id, path): + self.subject_id = subject_id + self.reference_id = reference_id + self.path = path + + +class XmlFileReader(xml.sax.handler.ContentHandler): + def __init__(self): + self.m_signature = None + self.m_path = None + self.m_presentation = None + self.m_file_list = dict() + + def startDocument(self): + pass + + def endDocument(self): + pass + + def startElement(self, name, attrs): + if name == "biometric-signature": + self.m_signature = attrs["name"] # subject_id + elif name == "presentation": + self.m_path = os.path.splitext(attrs["file-name"])[0] # path + self.m_presentation = attrs["name"] # reference_id + else: + pass + + def endElement(self, name): + if name == "biometric-signature": + # assert that everything was read correctly + assert ( + self.m_signature is not None + and self.m_path is not None + and self.m_presentation is not None + ) + # add a file to the sessions + self.m_file_list[self.m_presentation] = File( + subject_id_from_signature(self.m_signature), + self.m_presentation, + self.m_path, + ) + + self.m_presentation = self.m_signature = self.m_path = None + else: + pass -class GBUBioFile(FaceBioFile): - def __init__(self, f): - super(GBUBioFile, self).__init__(client_id=f.client_id, path=f.path, file_id=f.id) - self._f = f +def subject_id_from_signature(signature): + return int(signature[4:]) -class GBUBioDatabase(BioDatabase): +def read_list(xml_file, eye_file=None): + """Reads the xml list and attaches the eye files, if given""" + # create xml reading instance + handler = XmlFileReader() + xml.sax.parse(xml_file, handler) + return handler.m_file_list + + +class GBUDatabase(Database): """ - GBU database implementation of :py:class:`bob.bio.base.database.BioDatabase` interface. - It is an extension of an SQL-based database interface, which directly talks to GBU database, for - verification experiments (good to use in bob.bio.base framework). + The GBU (Good, Bad and Ugly) database consists of parts of the MBGC-V1 image set. + It defines three protocols, i.e., `Good`, `Bad` and `Ugly` for which different model and probe images are used. + + + .. warning:: + + To use this dataset protocol, you need to have the original files of the IJBC datasets. + Once you have it downloaded, please run the following command to set the path for Bob + + .. code-block:: sh + + bob config set bob.bio.face.gbu.directory [GBU PATH] + + + The code below allows you to fetch the galery and probes of the "Good" protocol. + + .. code-block:: python + + >>> from bob.bio.face.database import GBUDatabase + >>> gbu = GBUDatabase(protocol="Good") + >>> + >>> # Fetching the gallery + >>> references = gbu.references() + >>> # Fetching the probes + >>> probes = gbu.probes() + + """ def __init__( - self, - original_directory=None, - original_extension='.jpg', - **kwargs + self, + protocol, + annotation_type="eyes-center", + fixed_positions=None, + original_directory=rc.get("bob.bio.face.gbu.directory"), + extension=".jpg", ): - from bob.db.gbu.query import Database as LowLevelDatabase - self._db = LowLevelDatabase(original_directory, original_extension) - # call base class constructors to open a session to the database - super(GBUBioDatabase, self).__init__( - name='GBU', - original_directory=original_directory, - original_extension=original_extension, - **kwargs) + # self.filename = "/idiap/user/tpereira/gitlab/bob/bob.nightlies/temp/gbu.tar.gz" + # Downloading model if not exists + urls = GBUDatabase.urls() + self.filename = get_file( + "gbu-xmls.tar.gz", urls, file_hash="827de43434ee84020c6a949ece5e4a4d" + ) + + self.references_dict = {} + self.probes_dict = {} + + self.annotations = None + self.original_directory = original_directory + self.extension = extension + + self.background_samples = None + self._background_files = [ + "GBU_Training_Uncontrolledx1.xml", + "GBU_Training_Uncontrolledx2.xml", + "GBU_Training_Uncontrolledx4.xml", + "GBU_Training_Uncontrolledx8.xml", + ] + + super().__init__( + name="gbu", + protocol=protocol, + allow_scoring_with_all_biometric_references=True, + annotation_type="eyes-center", + fixed_positions=None, + memory_demanding=True, + ) + + @staticmethod + def protocols(): + return ["Good", "Bad", "Ugly"] + + @staticmethod + def urls(): + return [ + "https://www.idiap.ch/software/bob/databases/latest/gbu-xmls.tar.gz", + "http://www.idiap.ch/software/bob/databases/latest/gbu-xmls.tar.gz", + ] + + def background_model_samples(self): + if self.background_samples is None: + if self.annotations is None: + self.annotations = load_annotations( + search_file(self.filename, "alleyes.csv") + ) + # for + self.background_samples = [] + + for b_files in self._background_files: + + f = search_file(self.filename, f"{b_files}") + + self.background_samples += self._make_sampleset_from_filedict( + read_list(f) + ) + return self.background_samples + + def probes(self, group="dev"): + if self.protocol not in self.probes_dict: + if self.annotations is None: + self.annotations = load_annotations( + search_file(self.filename, "alleyes.csv") + ) + + f = search_file(self.filename, f"GBU_{self.protocol}_Query.xml") + reference_ids = [x.reference_id for x in self.references()] + + self.probes_dict[self.protocol] = self._make_sampleset_from_filedict( + read_list(f), reference_ids + ) + return self.probes_dict[self.protocol] + + def references(self, group="dev"): + + if self.protocol not in self.references_dict: + + if self.annotations is None: + self.annotations = load_annotations( + search_file(self.filename, "alleyes.csv") + ) + + f = search_file(self.filename, f"GBU_{self.protocol}_Target.xml") + self.references_dict[self.protocol] = self._make_sampleset_from_filedict( + read_list(f), + ) + + return self.references_dict[self.protocol] + + def groups(self): + return ["dev"] + + def all_samples(self, group="dev"): + self._check_group(group) + + return self.references() + self.probes() + + def _check_protocol(self, protocol): + assert protocol in self.protocols(), "Unvalid protocol `{}` not in {}".format( + protocol, self.protocols() + ) + + def _check_group(self, group): + assert group in self.groups(), "Unvalid group `{}` not in {}".format( + group, self.groups() + ) - @property - def original_directory(self): - return self._db.original_directory + def _make_sampleset_from_filedict(self, file_dict, reference_ids=None): + samplesets = [] + for key in file_dict: + f = file_dict[key] - @original_directory.setter - def original_directory(self, value): - self._db.original_directory = value + annotations_key = os.path.basename(f.path) - def model_ids_with_protocol(self, groups=None, protocol=None, **kwargs): - return self._db.model_ids(groups=groups, protocol=protocol) + kwargs = {"references": reference_ids} if reference_ids is not None else {} - def objects(self, groups=None, protocol=None, purposes=None, model_ids=None, **kwargs): - retval = self._db.objects(groups=groups, protocol=protocol, purposes=purposes, model_ids=model_ids, **kwargs) - return [GBUBioFile(f) for f in retval] + samplesets.append( + SampleSet( + key=f.path, + reference_id=f.reference_id, + subject_id=f.subject_id, + **kwargs, + samples=[ + DelayedSample( + key=f.path, + annotations=self.annotations[annotations_key], + load=partial( + bob.io.image.load, + os.path.join( + self.original_directory, f.path + self.extension + ), + ), + ) + ], + ) + ) + return samplesets - def annotations(self, myfile): - return self._db.annotations(myfile._f) diff --git a/bob/bio/face/test/test_databases.py b/bob/bio/face/test/test_databases.py index 77bc550e5e4ec8914834e64ffb04af691adadf84..0619f37ddb86c6ebec03d6396a3d7f392fced0df 100644 --- a/bob/bio/face/test/test_databases.py +++ b/bob/bio/face/test/test_databases.py @@ -450,3 +450,27 @@ def test_cbsr_nir_vis_2(): assert len(database.references()) == 358 assert len(database.probes()) == 6123 + + +@pytest.mark.skipif( + rc.get("bob.bio.face.gbu.directory") is None, + reason="GBU original protocols not available. Please do `bob config set bob.bio.face.gbu.directory [GBU PATH]` to set the GBU data path.", +) +def test_gbu(): + + from bob.bio.face.database import GBUDatabase + + database = GBUDatabase("Good") + assert len(database.references()) == 1085 + assert len(database.probes()) == 1085 + + database = GBUDatabase("Bad") + assert len(database.references()) == 1085 + assert len(database.probes()) == 1085 + + database = GBUDatabase("Ugly") + assert len(database.references()) == 1085 + assert len(database.probes()) == 1085 + + assert len(database.background_model_samples()) == 3910 + diff --git a/doc/implemented.rst b/doc/implemented.rst index f0695bc338d18329af07b3102bbc02fb8af451e7..7d7eca48710c53298d157b2c6ad226cf9ce07d0c 100644 --- a/doc/implemented.rst +++ b/doc/implemented.rst @@ -17,7 +17,7 @@ Databases bob.bio.face.database.IJBCDatabase bob.bio.face.database.ReplayBioDatabase bob.bio.face.database.ReplayMobileBioDatabase - bob.bio.face.database.GBUBioDatabase + bob.bio.face.database.GBUDatabase bob.bio.face.database.LFWBioDatabase bob.bio.face.database.MultipieDatabase bob.bio.face.database.FargoBioDatabase diff --git a/setup.py b/setup.py index 4513076e8567a61a54f96b7f800efd1613b66944..3546504ab3a168a58ca3b70f8932d626a675ab1c 100644 --- a/setup.py +++ b/setup.py @@ -99,7 +99,9 @@ setup( "atnt = bob.bio.face.config.database.atnt:database", "casia-africa = bob.bio.face.config.database.casia_africa:database", "fargo = bob.bio.face.config.database.fargo:database", - "gbu = bob.bio.face.config.database.gbu:database", + "gbu-good = bob.bio.face.config.database.gbu_good:database", + "gbu-bad = bob.bio.face.config.database.gbu_bad:database", + "gbu-ugly = bob.bio.face.config.database.gbu_ugly:database", "ijbc-11 = bob.bio.face.config.database.ijbc:database", "lfw-restricted = bob.bio.face.config.database.lfw_restricted:database", "lfw-unrestricted = bob.bio.face.config.database.lfw_unrestricted:database", @@ -171,7 +173,9 @@ setup( "atnt = bob.bio.face.config.database.atnt", "casia-africa = bob.bio.face.config.database.casia_africa", "fargo = bob.bio.face.config.database.fargo", - "gbu = bob.bio.face.config.database.gbu", + "gbu-good = bob.bio.face.config.database.gbu_good", + "gbu-bad = bob.bio.face.config.database.gbu_bad", + "gbu-ugly = bob.bio.face.config.database.gbu_ugly", "ijbc-11 = bob.bio.face.config.database.ijbc", "lfw-restricted = bob.bio.face.config.database.lfw_restricted", "lfw-unrestricted = bob.bio.face.config.database.lfw_unrestricted",