diff --git a/bob/pad/face/config/casiafasd.py b/bob/pad/face/config/casiafasd.py index a6e713511d43097e4894f2518c10eae6b950f186..991295545b9bbd5ed4bb34fb05f7d89d55acc1ee 100644 --- a/bob/pad/face/config/casiafasd.py +++ b/bob/pad/face/config/casiafasd.py @@ -1,5 +1,5 @@ """Config file for the CASIA FASD dataset. -Please run ``bob config set bob.db.casia_fasd.directory /path/to/casia_fasd_files`` +Please run ``bob config set bob.db.casia_fasd.directory /path/to/database/casia_fasd/`` in terminal to point to the original files of the dataset on your computer.""" from bob.pad.face.database import CasiaFasdPadDatabase diff --git a/bob/pad/face/config/casiasurf_color.py b/bob/pad/face/config/casiasurf_color.py deleted file mode 100644 index 7f9653eb448f587312f781c40a35a82be41a4cbe..0000000000000000000000000000000000000000 --- a/bob/pad/face/config/casiasurf_color.py +++ /dev/null @@ -1,3 +0,0 @@ -from bob.pad.face.database import CasiaSurfPadDatabase - -database = CasiaSurfPadDatabase(stream_type="color") diff --git a/bob/pad/face/config/deep_pix_bis.py b/bob/pad/face/config/deep_pix_bis.py index 6533e00ba6989dbb985c72456d43a983a5583511..61e2fa1916d94a41c6f1244d1ec677cfc41d24ee 100644 --- a/bob/pad/face/config/deep_pix_bis.py +++ b/bob/pad/face/config/deep_pix_bis.py @@ -1,6 +1,6 @@ """ Deep Pixel-wise Binary Supervision for Face PAD -This package contains source code to replicate the experimental results published in the following publication:: +This baseline includes the models to replicate the experimental results published in the following publication:: @INPROCEEDINGS{GeorgeICB2019, author = {Anjith George, Sebastien Marcel}, @@ -22,10 +22,12 @@ from bob.pad.face.deep_pix_bis import DeepPixBisClassifier from bob.pad.face.transformer import VideoToFrames database = globals().get("database") +annotation_type, fixed_positions = None, None if database is not None: annotation_type = database.annotation_type fixed_positions = database.fixed_positions -else: + +if annotation_type is None: annotation_type = "eyes-center" fixed_positions = None diff --git a/bob/pad/face/config/maskattack.py b/bob/pad/face/config/maskattack.py index 27dda510e59a2c9fc1efbad74364731ee267a738..7cb0915a0d436af9f4709e7b8e5387b2b73e0135 100644 --- a/bob/pad/face/config/maskattack.py +++ b/bob/pad/face/config/maskattack.py @@ -1,3 +1,24 @@ +"""The `Mask-Attack`_ database for face anti-spoofing +consists of video clips of mask attacks. This database was produced at the +`Idiap Research Institute <http://www.idiap.ch>`_, in Switzerland. + +If you use this database in your publication, please cite the following paper on +your references: + + + @INPROCEEDINGS{ERDOGMUS_BTAS-2013, + author = {Erdogmus, Nesli and Marcel, Sébastien}, + keywords = {biometric, Counter-Measures, Spoofing Attacks}, + month = september, + title = {Spoofing in 2D Face Recognition with 3D Masks and Anti-spoofing with Kinect}, + journal = {Biometrics: Theory, Applications and Systems}, + year = {2013},} + +After downloading, you can tell the bob library where the files are located +using:: + + $ bob config set bob.db.maskattack.directory /path/to/database/3dmad/Data/ +""" from bob.pad.face.database import MaskAttackPadDatabase database = MaskAttackPadDatabase() diff --git a/bob/pad/face/config/oulunpu.py b/bob/pad/face/config/oulunpu.py index d26e331206556dc3e03eafaa4f69a739966de587..241e6477ef5603cd7a461c7fba01df9901a370e5 100644 --- a/bob/pad/face/config/oulunpu.py +++ b/bob/pad/face/config/oulunpu.py @@ -3,7 +3,7 @@ A mobile face presentation attack database with real-world variations database. To configure the location of the database on your computer, run:: - bob config set bob.db.oulunpu.directory /path/to/oulunpu/database + bob config set bob.db.oulunpu.directory /path/to/database/oulu-npu If you use this database, please cite the following publication:: diff --git a/bob/pad/face/config/replay_attack.py b/bob/pad/face/config/replay_attack.py index 1c22f9a00d941386eb26eb5808de86d4d8b9d1b8..013c0ef8100da4df69d39d2cd38c1c3ffd518846 100644 --- a/bob/pad/face/config/replay_attack.py +++ b/bob/pad/face/config/replay_attack.py @@ -9,7 +9,7 @@ You can download the raw data of the `Replay-Attack`_ database by following the link. After downloading, you can tell the bob library where the files are located using:: - $ bob config set bob.db.replayattack.directory /path/to/replayattack/directory + $ bob config set bob.db.replayattack.directory /path/to/database/replay/protocols/replayattack-database/ """ from bob.pad.face.database import ReplayAttackPadDatabase diff --git a/bob/pad/face/config/replay_mobile.py b/bob/pad/face/config/replay_mobile.py index fde9ed011150ec3a520db22d7795630e3c229776..fb49887060486c93b22b4b1dfb7d7adb6ecd69b0 100644 --- a/bob/pad/face/config/replay_mobile.py +++ b/bob/pad/face/config/replay_mobile.py @@ -8,7 +8,10 @@ of collaboration with Galician Research and Development Center in Advanced Telec The reference citation is [CBVM16]_. You can download the raw data of the `Replay-Mobile`_ database by following -the link. +the link. After downloading, you can tell the bob library where the files are +located using:: + + $ bob config set bob.db.replaymobile.directory /path/to/database/replay-mobile/database/ """ from bob.pad.face.database import ReplayMobilePadDatabase diff --git a/bob/pad/face/config/svm_frames.py b/bob/pad/face/config/svm_frames.py index 73ece6023b3dc9dcc23882e6fe949c4f92148a1d..ebe9f1063db1185d6ae236e3e32662b6b7446371 100644 --- a/bob/pad/face/config/svm_frames.py +++ b/bob/pad/face/config/svm_frames.py @@ -10,7 +10,6 @@ preprocessor = globals()["preprocessor"] extractor = globals()["extractor"] # Classifier # - param_grid = [ { "C": [2**P for P in range(-3, 14, 2)], @@ -20,6 +19,12 @@ param_grid = [ ] +# TODO: The grid search below does not take into account splitting frames of +# each video into a separate group. You might have frames of the same video in +# both groups of training and validation. + +# TODO: This gridsearch can also be part of dask graph using dask-ml and the +# ``bob_fit_supports_dask_array`` tag from bob.pipelines. classifier = GridSearchCV(SVC(), param_grid=param_grid, cv=3) classifier = mario.wrap( ["sample"], diff --git a/bob/pad/face/config/swan.py b/bob/pad/face/config/swan.py index 0e1c3f17a2d8a7a711e72d694b445c6434f49906..3f8a7513853f1ba0622b06ec6f336425f937a282 100644 --- a/bob/pad/face/config/swan.py +++ b/bob/pad/face/config/swan.py @@ -2,7 +2,7 @@ To configure the location of the database on your computer, run:: - bob config set bob.db.swan.directory /path/to/swan/database + bob config set bob.db.swan.directory /path/to/database/swan The Idiap part of the dataset comprises 150 subjects that are captured in six diff --git a/bob/pad/face/database/casiasurf.py b/bob/pad/face/database/casiasurf.py index 652dbd00965f58a9c75b685340c31f77e2d873c1..79e82f31df6e750782604b8c3b0533fe87777ce2 100644 --- a/bob/pad/face/database/casiasurf.py +++ b/bob/pad/face/database/casiasurf.py @@ -9,68 +9,61 @@ import bob.io.base from bob.bio.video import VideoLikeContainer from bob.extension import rc -from bob.extension.download import get_file from bob.pad.base.database import FileListPadDatabase -from bob.pipelines import DelayedSample +from bob.pipelines import CSVToSamples, DelayedSample logger = logging.getLogger(__name__) -def load_multi_stream(mods, paths): - retval = {} - for mod, path in zip(mods, paths): - data = bob.io.base.load(path) - fc = VideoLikeContainer(data, [0]) - retval[mod] = fc +def load_multi_stream(path): + data = bob.io.base.load(path) + video = VideoLikeContainer(data[None, ...], [0]) + return video - if len(retval) == 1: - retval = retval[mods[0]] - return retval - - -def casia_surf_multistream_load(samples, original_directory, stream_type): +def casia_surf_multistream_load(samples, original_directory): mod_to_attr = {} mod_to_attr["color"] = "filename" mod_to_attr["infrared"] = "ir_filename" mod_to_attr["depth"] = "depth_filename" - - mods = [] - if isinstance(stream_type, str) and stream_type != "all": - mods = [stream_type] - elif isinstance(stream_type, str) and stream_type == "all": - mods = ["color", "infrared", "depth"] - else: - for m in stream_type: - mods.append(m) + mods = list(mod_to_attr.keys()) def _load(sample): - paths = [] + paths = dict() for mod in mods: - paths.append( - os.path.join( - original_directory or "", getattr(sample, mod_to_attr[mod]) - ) + paths[mod] = os.path.join( + original_directory or "", getattr(sample, mod_to_attr[mod]) ) - data = partial(load_multi_stream, mods, paths) - return DelayedSample(data, parent=sample, annotations=None) + data = partial(load_multi_stream, paths["color"]) + depth = partial(load_multi_stream, paths["depth"]) + infrared = partial(load_multi_stream, paths["infrared"]) + subject = None + key = sample.filename + is_bonafide = sample.is_bonafide == "1" + attack_type = None if is_bonafide else "attack" + + return DelayedSample( + data, + parent=sample, + subject=subject, + key=key, + attack_type=attack_type, + is_bonafide=is_bonafide, + annotations=None, + delayed_attributes={"depth": depth, "infrared": infrared}, + ) return [_load(s) for s in samples] -def CasiaSurfMultiStreamSample(original_directory, stream_type): +def CasiaSurfMultiStreamSample(original_directory): return FunctionTransformer( casia_surf_multistream_load, - kw_args=dict( - original_directory=original_directory, stream_type=stream_type - ), + kw_args=dict(original_directory=original_directory), ) -def CasiaSurfPadDatabase( - stream_type="all", - **kwargs, -): +class CasiaSurfPadDatabase(FileListPadDatabase): """The CASIA SURF Face PAD database interface. Parameters @@ -81,25 +74,50 @@ def CasiaSurfPadDatabase( The returned sample either have their data as a VideoLikeContainer or a dict of VideoLikeContainers depending on the chosen stream_type. """ - name = "pad-face-casia-surf-252f86f2.tar.gz" - dataset_protocols_path = get_file( - name, - [f"http://www.idiap.ch/software/bob/data/bob/bob.pad.face/{name}"], - cache_subdir="protocols", - file_hash="252f86f2", - ) - transformer = CasiaSurfMultiStreamSample( - original_directory=rc.get("bob.db.casiasurf.directory"), - stream_type=stream_type, - ) - - database = FileListPadDatabase( - dataset_protocols_path, - protocol="all", - transformer=transformer, + def __init__( + self, **kwargs, - ) - database.annotation_type = None - database.fixed_positions = None - return database + ): + original_directory = rc.get("bob.db.casiasurf.directory") + if original_directory is None or not os.path.isdir(original_directory): + raise FileNotFoundError( + "The original_directory is not set. Please set it in the terminal using `bob config set bob.db.casiasurf.directory /path/to/database/CASIA-SURF/`." + ) + transformer = CasiaSurfMultiStreamSample( + original_directory=original_directory, + ) + super().__init__( + dataset_protocols_path=original_directory, + protocol="all", + reader_cls=partial( + CSVToSamples, + dict_reader_kwargs=dict( + delimiter=" ", + fieldnames=[ + "filename", + "ir_filename", + "depth_filename", + "is_bonafide", + ], + ), + ), + transformer=transformer, + **kwargs, + ) + self.annotation_type = None + self.fixed_positions = None + + def protocols(self): + return ["all"] + + def groups(self): + return ["train", "dev", "eval"] + + def list_file(self, group): + filename = { + "train": "train_list.txt", + "dev": "val_private_list.txt", + "eval": "test_private_list.txt", + }[group] + return os.path.join(self.dataset_protocols_path, filename) diff --git a/bob/pad/face/database/database.py b/bob/pad/face/database/database.py index 32d04a3488e3e17c1736111d7ae0073c60e07a6b..e92a84811ae6dc681da34535db4727a2119259c1 100644 --- a/bob/pad/face/database/database.py +++ b/bob/pad/face/database/database.py @@ -49,6 +49,11 @@ def delayed_video_load( annotation_type="json", ) delayed_attributes = {"annotations": delayed_annotations} + if sample.attack_type == "": + sample.attack_type = None + sample.is_bonafide = sample.attack_type is None + if not hasattr(sample, "key"): + sample.key = sample.filename results.append( DelayedSample( diff --git a/bob/pad/face/database/maskattack.py b/bob/pad/face/database/maskattack.py index 60c3a6108a6da0d96180425e5edfd2e1cfd8a818..9de3df02a4888dcece0a295a66b3ad2aad75116d 100644 --- a/bob/pad/face/database/maskattack.py +++ b/bob/pad/face/database/maskattack.py @@ -1,21 +1,131 @@ import logging +import os +from functools import partial + +import h5py +import numpy as np + +from sklearn.preprocessing import FunctionTransformer + +from bob.bio.video import VideoLikeContainer, select_frames from bob.extension import rc from bob.extension.download import get_file from bob.pad.base.database import FileListPadDatabase -from bob.pad.face.database import VideoPadSample +from bob.pipelines import DelayedSample logger = logging.getLogger(__name__) +def load_frames_from_hdf5( + hdf5_file, + key="Color_Data", + selection_style=None, + max_number_of_frames=None, + step_size=None, +): + with h5py.File(hdf5_file) as f: + video = f[key][()] + # reduce the shape of depth from (N, C, H, W) to (N, H, W) since H == 1 + video = np.squeeze(video) + + indices = select_frames( + len(video), + max_number_of_frames=max_number_of_frames, + selection_style=selection_style, + step_size=step_size, + ) + data = VideoLikeContainer(video[indices], indices) + + return data + + +def load_annotations_from_hdf5( + hdf5_file, +): + with h5py.File(hdf5_file) as f: + eye_pos = f["Eye_Pos"][()] + + annotations = { + str(i): { + "reye": [row[1], row[0]], + "leye": [row[3], row[2]], + } + for i, row in enumerate(eye_pos) + } + return annotations + + +def delayed_maskattack_video_load( + samples, + original_directory, + selection_style=None, + max_number_of_frames=None, + step_size=None, +): + + original_directory = original_directory or "" + results = [] + for sample in samples: + hdf5_file = os.path.join(original_directory, sample.filename) + data = partial( + load_frames_from_hdf5, + key="Color_Data", + hdf5_file=hdf5_file, + selection_style=selection_style, + max_number_of_frames=max_number_of_frames, + step_size=step_size, + ) + depth = partial( + load_frames_from_hdf5, + key="Depth_Data", + hdf5_file=hdf5_file, + selection_style=selection_style, + max_number_of_frames=max_number_of_frames, + step_size=step_size, + ) + annotations = partial( + load_annotations_from_hdf5, + hdf5_file=hdf5_file, + ) + delayed_attributes = { + "annotations": annotations, + "depth": depth, + } + + results.append( + DelayedSample( + data, + parent=sample, + delayed_attributes=delayed_attributes, + ) + ) + return results + + +def MaskAttackPadSample( + original_directory, + selection_style=None, + max_number_of_frames=None, + step_size=None, +): + return FunctionTransformer( + delayed_maskattack_video_load, + validate=False, + kw_args=dict( + original_directory=original_directory, + selection_style=selection_style, + max_number_of_frames=max_number_of_frames, + step_size=step_size, + ), + ) + + def MaskAttackPadDatabase( protocol="classification", selection_style=None, max_number_of_frames=None, step_size=None, - annotation_directory=None, - annotation_type=None, - fixed_positions=None, **kwargs, ): name = "pad-face-mask-attack-211bd751.tar.gz" @@ -25,10 +135,10 @@ def MaskAttackPadDatabase( cache_subdir="protocols", file_hash="211bd751", ) + dataset_protocols_path = "/idiap/home/amohammadi/bob_data/protocols/pad-face-mask-attack-211bd751/" - transformer = VideoPadSample( + transformer = MaskAttackPadSample( original_directory=rc.get("bob.db.maskattack.directory"), - annotation_directory=annotation_directory, selection_style=selection_style, max_number_of_frames=max_number_of_frames, step_size=step_size, @@ -40,6 +150,6 @@ def MaskAttackPadDatabase( transformer=transformer, **kwargs, ) - database.annotation_type = annotation_type - database.fixed_positions = fixed_positions + database.annotation_type = "eyes-center" + database.fixed_positions = None return database diff --git a/bob/pad/face/test/test_databases.py b/bob/pad/face/test/test_databases.py index 511a8c4823aaf0ddc501daaa8a8075f13a95f716..6e2d47380503d315686d40fdcd3364539684fa2a 100644 --- a/bob/pad/face/test/test_databases.py +++ b/bob/pad/face/test/test_databases.py @@ -2,9 +2,9 @@ # vim: set fileencoding=utf-8 : # Thu May 24 10:41:42 CEST 2012 -import numpy as np +from unittest import SkipTest -from nose.plugins.skip import SkipTest +import numpy as np import bob.bio.base @@ -169,53 +169,19 @@ def test_maskattack(): assert len(maskattack.samples(groups=["dev"], purposes="attack")) == 25 assert len(maskattack.samples(groups=["eval"], purposes="attack")) == 25 - -# Test the casiasurf database -# def test_casiasurf(): -# casiasurf = bob.bio.base.load_resource( -# "casiasurf", -# "database", -# preferred_package="bob.pad.face", -# package_prefix="bob.pad.", -# ) -# assert len(casiasurf.samples(groups=["train"], purposes="real")) == 8942 -# assert len(casiasurf.samples(groups=["train"], purposes="attack")) == 20324 -# assert len(casiasurf.samples(groups=("dev",), purposes=("real",))) == 2994 -# assert len(casiasurf.samples(groups=("dev",), purposes=("attack",))) == 6614 -# assert ( -# len(casiasurf.samples(groups=("dev",), purposes=("real", "attack"))) == 9608 -# ) -# assert len(casiasurf.samples(groups=("eval",), purposes=("real",))) == 17458 -# assert len(casiasurf.samples(groups=("eval",), purposes=("attack",))) == 40252 -# assert ( -# len(casiasurf.samples(groups=("eval",), purposes=("real", "attack"))) -# == 57710 -# ) - - -def test_casiasurf_color_protocol(): - casiasurf = bob.bio.base.load_resource( - "casiasurf-color", - "database", - preferred_package="bob.pad.face", - package_prefix="bob.pad.", - ) - assert len(casiasurf.samples(groups=["train"], purposes="real")) == 8942 - assert len(casiasurf.samples(groups=["train"], purposes="attack")) == 20324 - assert len(casiasurf.samples(groups=("dev",), purposes=("real",))) == 2994 - assert len(casiasurf.samples(groups=("dev",), purposes=("attack",))) == 6614 - assert ( - len(casiasurf.samples(groups=("dev",), purposes=("real", "attack"))) - == 9608 - ) - assert len(casiasurf.samples(groups=("eval",), purposes=("real",))) == 17458 - assert ( - len(casiasurf.samples(groups=("eval",), purposes=("attack",))) == 40252 - ) - assert ( - len(casiasurf.samples(groups=("eval",), purposes=("real", "attack"))) - == 57710 - ) + sample = maskattack.samples()[0] + try: + assert sample.data.shape == (20, 3, 480, 640) + np.testing.assert_equal(sample.data[0][:, 0, 0], [185, 166, 167]) + annot = sample.annotations["0"] + assert annot["leye"][1] > annot["reye"][1], annot + assert annot == { + "leye": [212, 287], + "reye": [217, 249], + } + assert sample.depth.shape == (20, 480, 640) + except FileNotFoundError as e: + raise SkipTest(e) def test_casia_fasd(): @@ -233,6 +199,37 @@ def test_casia_fasd(): assert len(casia_fasd.samples(groups="train")) == 180 assert len(casia_fasd.samples(groups="dev")) == 60 assert len(casia_fasd.samples(groups="eval")) == 360 + sample = casia_fasd.samples()[0] + try: + assert sample.data.shape == (20, 3, 480, 640) + np.testing.assert_equal(sample.data[0][:, 0, 0], [217, 228, 227]) + except FileNotFoundError as e: + raise SkipTest(e) + + +def test_casia_surf(): + casia_surf = bob.bio.base.load_resource( + "casiasurf", + "database", + preferred_package="bob.pad.face", + package_prefix="bob.pad.", + ) + + try: + assert len(casia_surf.samples()) == 96584 + assert len(casia_surf.samples(purposes="real")) == 29394 + assert len(casia_surf.samples(purposes="attack")) == 67190 + assert len(casia_surf.samples(groups=("train", "dev"))) == 38874 + assert len(casia_surf.samples(groups="train")) == 29266 + assert len(casia_surf.samples(groups="dev")) == 9608 + assert len(casia_surf.samples(groups="eval")) == 57710 + sample = casia_surf.samples()[0] + assert sample.data.shape == (1, 3, 279, 279) + np.testing.assert_equal(sample.data[0][:, 0, 0], [0, 0, 0]) + assert sample.depth.shape == (1, 143, 143) + assert sample.infrared.shape == (1, 143, 143) + except FileNotFoundError as e: + raise SkipTest(e) def test_swan(): diff --git a/bob/pad/face/test/test_utils.py b/bob/pad/face/test/test_utils.py index 1bdc971d3cb452185234e639fa815a2ab8a33b96..e6bf7c0163cf03471acb3f1f32e8e7e05467a52a 100644 --- a/bob/pad/face/test/test_utils.py +++ b/bob/pad/face/test/test_utils.py @@ -1,7 +1,6 @@ import imageio import numpy - -from nose.tools import raises +import pytest from bob.pad.face.test.dummy.database import DummyDatabase as Database from bob.pad.face.utils import ( @@ -57,11 +56,11 @@ def test_yield_frames(): assert frame.shape == (112, 92) -@raises(ValueError) def test_yield_faces_1(): - padfile = get_pad_sample(none_annotations=True) - for face in yield_faces(padfile, dummy_cropper): - pass + with pytest.raises(ValueError): + padfile = get_pad_sample(none_annotations=True) + for face in yield_faces(padfile, dummy_cropper): + pass def test_yield_faces_2(): @@ -105,11 +104,11 @@ def test_blocks(): assert (patches_gray == patches[:, 2, ...]).all() -@raises(ValueError) def test_block_raises1(): - blocks(image[0], (28, 28)) + with pytest.raises(ValueError): + blocks(image[0], (28, 28)) -@raises(ValueError) def test_block_raises2(): - blocks([[[image]]], (28, 28)) + with pytest.raises(ValueError): + blocks([[[image]]], (28, 28)) diff --git a/bob/pad/face/transformer/VideoToFrames.py b/bob/pad/face/transformer/VideoToFrames.py index f04318bb220d832d0e89a095dde8057a1851c230..8a1bdc5eb31131b9c70a25cc1e9ec865e8371ab9 100644 --- a/bob/pad/face/transformer/VideoToFrames.py +++ b/bob/pad/face/transformer/VideoToFrames.py @@ -1,5 +1,7 @@ import logging +from functools import partial + from sklearn.base import BaseEstimator, TransformerMixin import bob.pipelines as mario @@ -9,6 +11,10 @@ from bob.pipelines.wrappers import _frmt logger = logging.getLogger(__name__) +def _get(sth): + return sth + + class VideoToFrames(TransformerMixin, BaseEstimator): """Expands video samples to frame-based samples only when transform is called.""" @@ -20,11 +26,15 @@ class VideoToFrames(TransformerMixin, BaseEstimator): # video is an instance of VideoAsArray or VideoLikeContainer video = sample.data + for frame, frame_id in zip(video, video.indices): if frame is None: continue - new_sample = mario.Sample( - frame, + # create a load method so that we can create DelayedSamples because + # the input samples could be DelayedSamples with delayed attributes + # as well and we don't want to load those delayed attributes. + new_sample = mario.DelayedSample( + partial(_get, frame), frame_id=frame_id, annotations=annotations.get(str(frame_id)), parent=sample, diff --git a/conda/meta.yaml b/conda/meta.yaml index e935d1bf2b22d528efc05c0a271482a437c2e148..79c2ee3c41e34da3cec4bffc3d4a8029fc15a80a 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -48,13 +48,14 @@ test: imports: - {{ name }} commands: - - nosetests --with-coverage --cover-package={{ name }} -sv {{ name }} + - pytest --verbose --cov {{ name }} --cov-report term-missing --cov-report html:{{ project_dir }}/sphinx/coverage --cov-report xml:{{ project_dir }}/coverage.xml --pyargs {{ name }} - sphinx-build -aEW {{ project_dir }}/doc {{ project_dir }}/sphinx - sphinx-build -aEb doctest {{ project_dir }}/doc sphinx - conda inspect linkages -p $PREFIX {{ name }} # [not win] - conda inspect objects -p $PREFIX {{ name }} # [osx] requires: - - nose {{ nose }} + - pytest {{ pytest }} + - pytest-cov {{ pytest_cov }} - coverage {{ coverage }} - sphinx {{ sphinx }} - sphinx_rtd_theme {{ sphinx_rtd_theme }} diff --git a/doc/baselines.rst b/doc/baselines.rst index 7c1095f1d397e554690edbee4f39bdcfe37bcba8..d389047aae7abab87d757b820a7a80af9db28bd4 100644 --- a/doc/baselines.rst +++ b/doc/baselines.rst @@ -230,4 +230,58 @@ which should give you:: AUC-LOG-SCALE 2.9 2.7 ====================== ============= ============ + +.. _bob.pad.face.baselines.swan: + +Baselines on SWAN database +-------------------------- + +This section summarizes the results of baseline face PAD experiments on the +`SWAN`_ database. The description of the database-related settings, +which are used to run face PAD baselines on the SWAN is given here +:ref:`bob.pad.face.resources.databases.swan`. To understand the +settings in more detail you can check the corresponding configuration file : +``bob/pad/face/config/swan.py``. + + +Deep-Pix-BiS Baseline +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: sh + + $ bob pad run-pipeline -vv swan deep-pix-bis --output <OUTPUT> --dask-client <CLIENT> + +This baseline reports scores per frame. To obtain scores per video, you can run:: + + $ bob pad finalize-scores -vv <OUTPUT>/scores-{dev,eval}.csv + +Finally, you can evaluate this baseline using:: + + $ bob pad metrics -vv --eval <OUTPUT>/scores-{dev,eval}.csv + +which should give you:: + + [Min. criterion: EER ] Threshold on Development set `<OUTPUT>/scores-dev.csv`: 4.867174e-01 + ============== ============== ================ + .. Development Evaluation + ============== ============== ================ + APCER (PA.F.1) 60.0% 51.1% + APCER (PA.F.5) 0.8% 2.8% + APCER (PA.F.6) 16.8% 16.3% + APCER_AP 60.0% 51.1% + BPCER 11.7% 21.8% + ACER 35.8% 36.5% + FTA 0.0% 0.0% + FPR 11.8% (59/502) 11.9% (89/749) + FNR 11.7% (35/300) 21.8% (491/2250) + HTER 11.7% 16.9% + FAR 11.8% 11.9% + FRR 11.7% 21.8% + PRECISION 0.8 1.0 + RECALL 0.9 0.8 + F1_SCORE 0.8 0.9 + AUC 1.0 0.9 + AUC-LOG-SCALE 2.0 1.6 + ============== ============== ================ + .. include:: links.rst diff --git a/doc/resources.rst b/doc/resources.rst index 4a37091372e3228f940c8901346d300e36f614e1..32d4bf64224cb6ed3ba521677999abe87e844a16 100644 --- a/doc/resources.rst +++ b/doc/resources.rst @@ -55,6 +55,16 @@ OULU-NPU Database +.. _bob.pad.face.resources.databases.swan: + +SWAN Database +================================================================================ + +.. automodule:: bob.pad.face.config.swan + :members: + + + .. _bob.pad.face.resources.deep_pix_bis_pad: Deep Pixel-wise Binary Supervision for Face PAD diff --git a/setup.py b/setup.py index 9e8487416a6d4628eec4511825035fd3f9ba5ad2..71ab6c238037d4cd7572d7ebd5f0c3e9fa1fa4e5 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,6 @@ setup( "replay-mobile = bob.pad.face.config.replay_mobile", "casiafasd = bob.pad.face.config.casiafasd", "maskattack = bob.pad.face.config.maskattack", - "casiasurf-color = bob.pad.face.config.casiasurf_color", "casiasurf = bob.pad.face.config.casiasurf", "swan = bob.pad.face.config.swan", "oulunpu = bob.pad.face.config.oulunpu", diff --git a/test-requirements.txt b/test-requirements.txt index f3c7e8e6ffb905f7de8b597eb22213a7dc20bfb3..e079f8a6038dd2dc8512967540f96ee0de172067 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1 @@ -nose +pytest