diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..2534a45ee555bb5ebefd37210d19399634c7a4ee --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 80 +ignore = E501,W503,E302,E402,E203 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1515754e0bef52e755e793e05c3400ecf532c4c7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,27 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/timothycrosley/isort + rev: 5.9.3 + hooks: + - id: isort + args: [--settings-path, "pyproject.toml"] + - repo: https://github.com/psf/black + rev: 21.7b0 + hooks: + - id: black + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: check-ast + - id: check-case-conflict + - id: trailing-whitespace + - id: end-of-file-fixer + - id: debug-statements + - id: check-added-large-files + - id: check-yaml + exclude: .*/meta.yaml diff --git a/MANIFEST.in b/MANIFEST.in index 032a1c60d657027bf19bc2e99186471180495ec4..beaa8a88201e31c8a915a4c835394350c1f2fde9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,4 +4,3 @@ recursive-include bob/pad/face/config/preprocessor/dictionaries *.hdf5 recursive-include doc *.py *.rst *.ico *.png recursive-include bob/pad/face/test/data *.hdf5 *.png recursive-include bob/pad/face/config *.xml - diff --git a/bob/__init__.py b/bob/__init__.py index 2ab1e28b150f0549def9963e9e87de3fdd6b2579..edbb4090fca046b19d22d3982711084621bff3be 100644 --- a/bob/__init__.py +++ b/bob/__init__.py @@ -1,3 +1,4 @@ # see https://docs.python.org/3/library/pkgutil.html from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) diff --git a/bob/pad/__init__.py b/bob/pad/__init__.py index 2ab1e28b150f0549def9963e9e87de3fdd6b2579..edbb4090fca046b19d22d3982711084621bff3be 100644 --- a/bob/pad/__init__.py +++ b/bob/pad/__init__.py @@ -1,3 +1,4 @@ # see https://docs.python.org/3/library/pkgutil.html from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) diff --git a/bob/pad/face/__init__.py b/bob/pad/face/__init__.py index b8a1d1248f4894fef9d5320ea15c3361765caef6..710ede733751925ecd9639629d72050926834057 100644 --- a/bob/pad/face/__init__.py +++ b/bob/pad/face/__init__.py @@ -1,13 +1,13 @@ -from . import extractor, preprocessor, database +from . import database, extractor, preprocessor # noqa: F401 def get_config(): - """Returns a string containing the configuration information. - """ + """Returns a string containing the configuration information.""" import bob.extension + return bob.extension.get_config(__name__) # gets sphinx autodoc done right - don't remove it -__all__ = [_ for _ in dir() if not _.startswith('_')] +__all__ = [_ for _ in dir() if not _.startswith("_")] diff --git a/bob/pad/face/config/svm_frames.py b/bob/pad/face/config/svm_frames.py index 8e17dbf5161d03b648c073aeb604ac5733dd4b9f..2aaa10151390aeba1c72ad3c20e9ff6b2570291e 100644 --- a/bob/pad/face/config/svm_frames.py +++ b/bob/pad/face/config/svm_frames.py @@ -1,9 +1,11 @@ -import bob.pipelines as mario -from bob.pad.face.transformer import VideoToFrames from sklearn.model_selection import GridSearchCV from sklearn.pipeline import Pipeline from sklearn.svm import SVC +import bob.pipelines as mario + +from bob.pad.face.transformer import VideoToFrames + preprocessor = globals().get("preprocessor") extractor = globals().get("extractor") @@ -21,7 +23,9 @@ param_grid = [ classifier = GridSearchCV(SVC(), param_grid=param_grid, cv=3) classifier = mario.wrap( - ["sample"], classifier, fit_extra_arguments=[("y", "is_bonafide")], + ["sample"], + classifier, + fit_extra_arguments=[("y", "is_bonafide")], ) diff --git a/bob/pad/face/database/__init__.py b/bob/pad/face/database/__init__.py index 3a831f7fbd1335f23dceca7be79e501d41be12a4..e30b9a7f52a4bbccb24f306dfb02e4b6864a658d 100644 --- a/bob/pad/face/database/__init__.py +++ b/bob/pad/face/database/__init__.py @@ -1,24 +1,24 @@ -from .database import VideoPadSample # noqa: F401 from .casiafasd import CasiaFasdPadDatabase from .casiasurf import CasiaSurfPadDatabase +from .database import VideoPadSample # noqa: F401 from .maskattack import MaskAttackPadDatabase +from .oulunpu import OulunpuPadDatabase from .replay_attack import ReplayAttackPadDatabase from .replay_mobile import ReplayMobilePadDatabase from .swan import SwanPadDatabase -from .oulunpu import OulunpuPadDatabase # gets sphinx autodoc done right - don't remove it def __appropriate__(*args): """Says object was actually declared here, and not in the import module. - Fixing sphinx warnings of not being able to find classes, when path is - shortened. Parameters: + Fixing sphinx warnings of not being able to find classes, when path is + shortened. Parameters: - *args: An iterable of objects to modify + *args: An iterable of objects to modify - Resolves `Sphinx referencing issues - <https://github.com/sphinx-doc/sphinx/issues/3048>` - """ + Resolves `Sphinx referencing issues + <https://github.com/sphinx-doc/sphinx/issues/3048>` + """ for obj in args: obj.__module__ = __name__ @@ -34,4 +34,4 @@ __appropriate__( OulunpuPadDatabase, ) -__all__ = [_ for _ in dir() if not _.startswith('_')] +__all__ = [_ for _ in dir() if not _.startswith("_")] diff --git a/bob/pad/face/database/casiasurf.py b/bob/pad/face/database/casiasurf.py index 68f34f86a86fa554409418b412328895451a6dae..652dbd00965f58a9c75b685340c31f77e2d873c1 100644 --- a/bob/pad/face/database/casiasurf.py +++ b/bob/pad/face/database/casiasurf.py @@ -1,14 +1,17 @@ import logging import os + from functools import partial +from sklearn.preprocessing import FunctionTransformer + 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 sklearn.preprocessing import FunctionTransformer logger = logging.getLogger(__name__) @@ -45,7 +48,9 @@ def casia_surf_multistream_load(samples, original_directory, stream_type): paths = [] for mod in mods: paths.append( - os.path.join(original_directory or "", getattr(sample, mod_to_attr[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) @@ -56,7 +61,9 @@ def casia_surf_multistream_load(samples, original_directory, stream_type): def CasiaSurfMultiStreamSample(original_directory, stream_type): return FunctionTransformer( casia_surf_multistream_load, - kw_args=dict(original_directory=original_directory, stream_type=stream_type), + kw_args=dict( + original_directory=original_directory, stream_type=stream_type + ), ) diff --git a/bob/pad/face/database/database.py b/bob/pad/face/database/database.py index b32aa67b1d78af4c97edb60eace8ce1060ebb50c..32d04a3488e3e17c1736111d7ae0073c60e07a6b 100644 --- a/bob/pad/face/database/database.py +++ b/bob/pad/face/database/database.py @@ -1,16 +1,18 @@ -from functools import partial import os -import bob.bio.video -from bob.bio.base.utils.annotations import read_annotation_file + +from functools import partial + from sklearn.preprocessing import FunctionTransformer + +from bob.bio.base.utils.annotations import read_annotation_file from bob.bio.video import VideoAsArray from bob.pipelines import DelayedSample -from ..utils import frames, number_of_frames def get_no_transform(x): return None + def delayed_video_load( samples, original_directory, diff --git a/bob/pad/face/database/replay_mobile.py b/bob/pad/face/database/replay_mobile.py index 08f6f7fd7e2faf7ecb5b66e5f02ad71f9d8e269a..3ac0550cef4f28a4310a5cd58d28ab92b7ab77e4 100644 --- a/bob/pad/face/database/replay_mobile.py +++ b/bob/pad/face/database/replay_mobile.py @@ -1,13 +1,12 @@ import logging -import numpy as np +from sklearn.pipeline import make_pipeline + 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.transformers import Str_To_Types -from bob.pipelines.transformers import str_to_bool -from sklearn.pipeline import make_pipeline +from bob.pipelines.transformers import Str_To_Types, str_to_bool logger = logging.getLogger(__name__) diff --git a/bob/pad/face/extractor/__init__.py b/bob/pad/face/extractor/__init__.py index 967814c8227ed65640d25919c445673eefb890b6..7688ad81bdc108f93b0c92ec1a209e194473ceaa 100644 --- a/bob/pad/face/extractor/__init__.py +++ b/bob/pad/face/extractor/__init__.py @@ -1,5 +1,3 @@ - - def __appropriate__(*args): """Says object was actually declared here, and not in the import module. Fixing sphinx warnings of not being able to find classes, when path is @@ -19,4 +17,4 @@ def __appropriate__(*args): __appropriate__() -__all__ = [_ for _ in dir() if not _.startswith('_')] +__all__ = [_ for _ in dir() if not _.startswith("_")] diff --git a/bob/pad/face/preprocessor/Patch.py b/bob/pad/face/preprocessor/Patch.py index 58503190310334d1394cdedb95d2aa709612bc0c..48d57c7c1bd1c5998c336c197d0205d697a93b06 100644 --- a/bob/pad/face/preprocessor/Patch.py +++ b/bob/pad/face/preprocessor/Patch.py @@ -1,8 +1,11 @@ -from bob.bio.base.annotator.FailSafe import translate_kwargs +from collections import OrderedDict + from sklearn.base import BaseEstimator, TransformerMixin -from ..utils import extract_patches + +from bob.bio.base.annotator.FailSafe import translate_kwargs from bob.bio.video import VideoLikeContainer -from collections import OrderedDict + +from ..utils import extract_patches class ImagePatches(TransformerMixin, BaseEstimator): @@ -55,7 +58,10 @@ class VideoPatches(TransformerMixin, BaseEstimator): def transform(self, videos, annotations=None): kwargs = translate_kwargs(dict(annotations=annotations), len(videos)) - return [self.transform_one_video(vid, **kw) for vid, kw in zip(videos, kwargs)] + return [ + self.transform_one_video(vid, **kw) + for vid, kw in zip(videos, kwargs) + ] def transform_one_video(self, frames, annotations=None): annotations = annotations or {} @@ -76,7 +82,10 @@ class VideoPatches(TransformerMixin, BaseEstimator): # extract patches patches = extract_patches( - preprocessed, self.block_size, self.block_overlap, self.n_random_patches + preprocessed, + self.block_size, + self.block_overlap, + self.n_random_patches, ) all_patches.extend(patches) diff --git a/bob/pad/face/preprocessor/__init__.py b/bob/pad/face/preprocessor/__init__.py index f1248dbf31454d131fe26356cb3404a6e472a6e2..c83f67c25c73722dc0b6e8f87a83d99e2d9e5d02 100644 --- a/bob/pad/face/preprocessor/__init__.py +++ b/bob/pad/face/preprocessor/__init__.py @@ -23,4 +23,4 @@ __appropriate__( ImagePatches, VideoPatches, ) -__all__ = [_ for _ in dir() if not _.startswith('_')] +__all__ = [_ for _ in dir() if not _.startswith("_")] diff --git a/bob/pad/face/script/statistics.py b/bob/pad/face/script/statistics.py index 0550d4ddc80bdbb6dae6d31d2c1a2a136c2c3950..8aa9a1bbb04386fd1f1984dacfeb4e83879379de 100644 --- a/bob/pad/face/script/statistics.py +++ b/bob/pad/face/script/statistics.py @@ -1,18 +1,21 @@ """Gets statistics on the average face size in a video database. """ import logging + +from os.path import expanduser + import click import numpy as np -from os.path import expanduser -from bob.extension.scripts.click_helper import ( - verbosity_option, - ConfigCommand, - ResourceOption, -) + from bob.bio.face.annotator import ( + BoundingBox, bounding_box_from_annotation, expected_eye_positions, - BoundingBox, +) +from bob.extension.scripts.click_helper import ( + ConfigCommand, + ResourceOption, + verbosity_option, ) logger = logging.getLogger(__name__) @@ -108,14 +111,20 @@ def statistics(database, output, database_directories_file, **kwargs): ): click.echo( "min: {}, mean: {}, max: {}, std: {:.1f} for {}".format( - array.min(), int(array.mean()), array.max(), array.std(), name + array.min(), + int(array.mean()), + array.max(), + array.std(), + name, ) ) # print the average eye distance assuming bounding boxes are from # bob.ip.facedetect or the annotations had eye locations in them bbx = BoundingBox((0, 0), face_sizes.mean(axis=0)) annot = expected_eye_positions(bbx) - eye_distance = np.linalg.norm(np.array(annot["reye"]) - np.array(annot["leye"])) + eye_distance = np.linalg.norm( + np.array(annot["reye"]) - np.array(annot["leye"]) + ) click.echo("Average eye locations: {}".format(annot)) click.echo("Average eye distance: {}".format(int(eye_distance))) @@ -141,7 +150,11 @@ def statistics(database, output, database_directories_file, **kwargs): # ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.viridis) plt.hist( - face_sizes[:, 1], density=True, bins="auto", label=attack_type, alpha=0.5 + face_sizes[:, 1], + density=True, + bins="auto", + label=attack_type, + alpha=0.5, ) if output: plt.xlabel("Width of faces") diff --git a/bob/pad/face/test/dummy/database.py b/bob/pad/face/test/dummy/database.py index 3928d5e797a8f1f36555794396ef79264248aaac..00cbeec41a817f115dea5f07dc00c7fc0f1c03c4 100644 --- a/bob/pad/face/test/dummy/database.py +++ b/bob/pad/face/test/dummy/database.py @@ -1,14 +1,24 @@ -import bob.io.base import os -from bob.pipelines import DelayedSample -from bob.pad.base.pipelines.vanilla_pad.abstract_classes import Database -from bob.bio.base.database.legacy import check_parameters_for_validity, convert_names_to_lowlevel -from bob.bio.video import VideoLikeContainer + +import bob.io.base + from bob.bio.base.database import AtntBioDatabase +from bob.bio.base.database.legacy import ( + check_parameters_for_validity, + convert_names_to_lowlevel, +) +from bob.bio.video import VideoLikeContainer +from bob.pad.base.pipelines.vanilla_pad.abstract_classes import Database +from bob.pipelines import DelayedSample def DummyPadSample( - path, original_directory, client_id, key, attack_type, none_annotations=False + path, + original_directory, + client_id, + key, + attack_type, + none_annotations=False, ): def load(): file_name = os.path.join(original_directory, path + ".pgm") diff --git a/bob/pad/face/test/test_block.py b/bob/pad/face/test/test_block.py index 6c23374e5e532230e497e3b25a1040a643229ac3..594eec85a71d11c168d544c6e624bd8339177824 100644 --- a/bob/pad/face/test/test_block.py +++ b/bob/pad/face/test/test_block.py @@ -2,11 +2,19 @@ import numpy A_org = numpy.array(range(1, 17), "float64").reshape((4, 4)) A_ans_0_3D = numpy.array( - [[[1, 2], [5, 6]], [[3, 4], [7, 8]], [[9, 10], [13, 14]], [[11, 12], [15, 16]]], + [ + [[1, 2], [5, 6]], + [[3, 4], [7, 8]], + [[9, 10], [13, 14]], + [[11, 12], [15, 16]], + ], "float64", ) A_ans_0_4D = numpy.array( - [[[[1, 2], [5, 6]], [[3, 4], [7, 8]]], [[[9, 10], [13, 14]], [[11, 12], [15, 16]]]], + [ + [[[1, 2], [5, 6]], [[3, 4], [7, 8]]], + [[[9, 10], [13, 14]], [[11, 12], [15, 16]]], + ], "float64", ) diff --git a/bob/pad/face/test/test_databases.py b/bob/pad/face/test/test_databases.py index 0683529a1034e167b5c0013c23671cfbb9b9d106..511a8c4823aaf0ddc501daaa8a8075f13a95f716 100644 --- a/bob/pad/face/test/test_databases.py +++ b/bob/pad/face/test/test_databases.py @@ -2,10 +2,11 @@ # vim: set fileencoding=utf-8 : # Thu May 24 10:41:42 CEST 2012 +import numpy as np + from nose.plugins.skip import SkipTest import bob.bio.base -import numpy as np def test_replayattack(): @@ -31,10 +32,13 @@ def test_replayattack(): assert len(database.samples(groups=["train", "dev"])) == 720 assert len(database.samples(groups=["train"])) == 360 assert ( - len(database.samples(groups=["train", "dev", "eval"], purposes="real")) == 200 + len(database.samples(groups=["train", "dev", "eval"], purposes="real")) + == 200 ) assert ( - len(database.samples(groups=["train", "dev", "eval"], purposes="attack")) + len( + database.samples(groups=["train", "dev", "eval"], purposes="attack") + ) == 1000 ) @@ -71,10 +75,14 @@ def test_replaymobile(): assert len(database.samples(groups=["train", "dev"])) == 728 assert len(database.samples(groups=["train"])) == 312 assert ( - len(database.samples(groups=["train", "dev", "eval"], purposes="real")) == 390 + len(database.samples(groups=["train", "dev", "eval"], purposes="real")) + == 390 ) assert ( - len(database.samples(groups=["train", "dev", "eval"], purposes="attack")) == 640 + len( + database.samples(groups=["train", "dev", "eval"], purposes="attack") + ) + == 640 ) all_samples = database.sort(database.samples()) @@ -133,11 +141,18 @@ def test_maskattack(): ) # all real sequences: 2 sessions, 5 recordings for 17 individuals assert ( - len(maskattack.samples(groups=["train", "dev", "eval"], purposes="real")) == 170 + len( + maskattack.samples(groups=["train", "dev", "eval"], purposes="real") + ) + == 170 ) # all attacks: 1 session, 5 recordings for 17 individuals assert ( - len(maskattack.samples(groups=["train", "dev", "eval"], purposes="attack")) + len( + maskattack.samples( + groups=["train", "dev", "eval"], purposes="attack" + ) + ) == 85 ) @@ -189,11 +204,17 @@ def test_casiasurf_color_protocol(): 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=("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 + len(casiasurf.samples(groups=("eval",), purposes=("attack",))) == 40252 + ) + assert ( + len(casiasurf.samples(groups=("eval",), purposes=("real", "attack"))) + == 57710 ) @@ -234,10 +255,13 @@ def test_swan(): assert len(database.samples(groups=["train", "dev"])) == 2803 assert len(database.samples(groups=["train"])) == 2001 assert ( - len(database.samples(groups=["train", "dev", "eval"], purposes="real")) == 3300 + len(database.samples(groups=["train", "dev", "eval"], purposes="real")) + == 3300 ) assert ( - len(database.samples(groups=["train", "dev", "eval"], purposes="attack")) + len( + database.samples(groups=["train", "dev", "eval"], purposes="attack") + ) == 2502 ) @@ -287,7 +311,10 @@ def test_oulunpu(): "Protocol_4_6", ] assert database.groups() == ["dev", "eval", "train"] - assert len(database.samples(groups=["train", "dev", "eval"])) == 1200 + 900 + 600 + assert ( + len(database.samples(groups=["train", "dev", "eval"])) + == 1200 + 900 + 600 + ) assert len(database.samples(groups=["train", "dev"])) == 1200 + 900 assert len(database.samples(groups=["train"])) == 1200 assert ( @@ -295,7 +322,9 @@ def test_oulunpu(): == 240 + 180 + 120 ) assert ( - len(database.samples(groups=["train", "dev", "eval"], purposes="attack")) + len( + database.samples(groups=["train", "dev", "eval"], purposes="attack") + ) == 960 + 720 + 480 ) diff --git a/bob/pad/face/test/test_transformers.py b/bob/pad/face/test/test_transformers.py index fb6989bc09cabc1093b59ba307e60bfd1c5851f9..90d3209cf79f205a334f19b6bb21f93b4988492c 100644 --- a/bob/pad/face/test/test_transformers.py +++ b/bob/pad/face/test/test_transformers.py @@ -1,4 +1,5 @@ import bob.pipelines as mario + from bob.bio.video import VideoLikeContainer from bob.pad.face.transformer import VideoToFrames diff --git a/bob/pad/face/test/test_utils.py b/bob/pad/face/test/test_utils.py index 609b109b374fa9a566c05528bc21499be79f1195..1bdc971d3cb452185234e639fa815a2ab8a33b96 100644 --- a/bob/pad/face/test/test_utils.py +++ b/bob/pad/face/test/test_utils.py @@ -1,12 +1,22 @@ -from bob.pad.face.test.dummy.database import DummyDatabase as Database -from bob.pad.face.utils import yield_faces, scale_face, blocks, frames, number_of_frames -from nose.tools import raises -import numpy import imageio +import numpy + +from nose.tools import raises + +from bob.pad.face.test.dummy.database import DummyDatabase as Database +from bob.pad.face.utils import ( + blocks, + frames, + number_of_frames, + scale_face, + yield_faces, +) def get_pad_sample(none_annotations=False): - sample = Database(none_annotations=none_annotations).samples(("train", "dev"))[0] + sample = Database(none_annotations=none_annotations).samples( + ("train", "dev") + )[0] return sample @@ -15,7 +25,9 @@ image = get_pad_sample().data[0] def test_video_frames(): # get the path to cockatoo.mp4 from imageio-ffmpeg - path = imageio.core.Request("imageio:cockatoo.mp4", "r").get_local_filename() + path = imageio.core.Request( + "imageio:cockatoo.mp4", "r" + ).get_local_filename() # read 2 frames for i, frame in enumerate(frames(path)): assert frame.shape == (3, 720, 1280), frame.shape @@ -32,7 +44,6 @@ def test_video_frames(): assert n_frames == 280, n_frames - def dummy_cropper(frame, annotations=None): return frame diff --git a/bob/pad/face/transformer/VideoToFrames.py b/bob/pad/face/transformer/VideoToFrames.py index 45351b77c98983ec6d4116e6e9c29560d318026a..f88bb7b96007b56e3ca45ec145ce26bc4895806c 100644 --- a/bob/pad/face/transformer/VideoToFrames.py +++ b/bob/pad/face/transformer/VideoToFrames.py @@ -1,14 +1,16 @@ -from sklearn.base import TransformerMixin, BaseEstimator +import logging + +from sklearn.base import BaseEstimator, TransformerMixin + import bob.pipelines as mario + from bob.pipelines.wrappers import _frmt -import logging logger = logging.getLogger(__name__) class VideoToFrames(TransformerMixin, BaseEstimator): - """Expands video samples to frame-based samples only when transform is called. - """ + """Expands video samples to frame-based samples only when transform is called.""" def transform(self, video_samples): logger.debug(f"{_frmt(self)}.transform") diff --git a/bob/pad/face/transformer/__init__.py b/bob/pad/face/transformer/__init__.py index 1307b1b59638c03a4b3056ca8289ac934682aee8..f5f5aaa6bf3bcd0e05162a2efbea65af42054692 100644 --- a/bob/pad/face/transformer/__init__.py +++ b/bob/pad/face/transformer/__init__.py @@ -22,4 +22,4 @@ def __appropriate__(*args): __appropriate__( VideoToFrames, ) -__all__ = [_ for _ in dir() if not _.startswith('_')] +__all__ = [_ for _ in dir() if not _.startswith("_")] diff --git a/bob/pad/face/utils/__init__.py b/bob/pad/face/utils/__init__.py index 2a16d37923d62d018f86441141f69fdbb60f7600..dffa28ce76788db55380bf6820103bea33c022d4 100644 --- a/bob/pad/face/utils/__init__.py +++ b/bob/pad/face/utils/__init__.py @@ -1,8 +1,18 @@ -from .load_utils import ( - frames, number_of_frames, yield_faces, scale_face, blocks, bbx_cropper, - min_face_size_normalizer, color_augmentation, blocks_generator, - the_giant_video_loader, random_sample, random_patches, extract_patches +from .load_utils import ( # noqa: F401 + bbx_cropper, + blocks, + blocks_generator, + color_augmentation, + extract_patches, + frames, + min_face_size_normalizer, + number_of_frames, + random_patches, + random_sample, + scale_face, + the_giant_video_loader, + yield_faces, ) # gets sphinx autodoc done right - don't remove it -__all__ = [_ for _ in dir() if not _.startswith('_')] +__all__ = [_ for _ in dir() if not _.startswith("_")] diff --git a/bob/pad/face/utils/load_utils.py b/bob/pad/face/utils/load_utils.py index 04f82a8b76f320efcc905faab5e284da49743bfd..7fc47a6a913a0d4091aef941a3e793d2247077dd 100644 --- a/bob/pad/face/utils/load_utils.py +++ b/bob/pad/face/utils/load_utils.py @@ -1,15 +1,22 @@ import random + from collections import OrderedDict from functools import partial -import bob.io.image import numpy -from bob.bio.face.annotator import bounding_box_from_annotation, min_face_size_validator -from bob.bio.video.annotator import normalize_annotations -from bob.bio.face.color import rgb_to_hsv, rgb_to_yuv + from imageio import get_reader from PIL import Image +import bob.io.image + +from bob.bio.face.annotator import ( + bounding_box_from_annotation, + min_face_size_validator, +) +from bob.bio.face.color import rgb_to_hsv, rgb_to_yuv +from bob.bio.video.annotator import normalize_annotations + def block(X, block_size, block_overlap, flat=False): """ @@ -46,7 +53,9 @@ def block(X, block_size, block_overlap, flat=False): ] if flat: - return blocks.reshape(n_blocks_h * n_blocks_w, blocks.shape[2], blocks.shape[3]) + return blocks.reshape( + n_blocks_h * n_blocks_w, blocks.shape[2], blocks.shape[3] + ) return blocks @@ -69,7 +78,9 @@ def scale(image, scaling_factor): """ if isinstance(scaling_factor, float): - new_size = tuple((numpy.array(image.shape) * scaling_factor).astype(numpy.int)) + new_size = tuple( + (numpy.array(image.shape) * scaling_factor).astype(numpy.int) + ) return bob.io.image.to_bob( numpy.array( Image.fromarray(bob.io.image.to_matplotlib(image)).resize( @@ -264,6 +275,61 @@ def blocks(data, block_size, block_overlap=(0, 0)): return output +def block_generator(input, block_size, block_overlap=(0, 0)): + """Performs a block decomposition of a 2D or 3D array/image + + It works exactly as :any:`bob.ip.base.block` except that it yields the blocks + one by one instead of concatenating them. It also works with color images. + + Parameters + ---------- + input : :any:`numpy.ndarray` + A 2D array (Height, Width) or a color image (Bob format: Channels, + Height, Width). + block_size : (:obj:`int`, :obj:`int`) + The size of the blocks in which the image is decomposed. + block_overlap : (:obj:`int`, :obj:`int`), optional + The overlap of the blocks. + + Yields + ------ + array_like + A block view of the image. Modifying the blocks will change the original + image as well. This is different from :any:`bob.ip.base.block`. + + Raises + ------ + ValueError + If the block_overlap is not smaller than block_size. + If the block_size is bigger than the image size. + """ + block_h, block_w = block_size + overlap_h, overlap_w = block_overlap + img_h, img_w = input.shape[-2:] + + if overlap_h >= block_h or overlap_w >= block_w: + raise ValueError( + "block_overlap: {} must be smaller than block_size: {}.".format( + block_overlap, block_size + ) + ) + if img_h < block_h or img_w < block_w: + raise ValueError( + "block_size: {} must be smaller than the image size: {}.".format( + block_size, input.shape[-2:] + ) + ) + + # Determine the number of block per row and column + size_ov_h = block_h - overlap_h + size_ov_w = block_w - overlap_w + + # Perform the block decomposition + for h in range(0, img_h - block_h + 1, size_ov_h): + for w in range(0, img_w - block_w + 1, size_ov_w): + yield input[..., h : h + block_h, w : w + block_w] + + def blocks_generator(data, block_size, block_overlap=(0, 0)): """Yields patches of an image @@ -347,12 +413,16 @@ def random_patches(image, block_size, n_random_patches=1): yield image[..., ch : ch + bh, cw : cw + bw] -def extract_patches(image, block_size, block_overlap=(0, 0), n_random_patches=None): +def extract_patches( + image, block_size, block_overlap=(0, 0), n_random_patches=None +): """Yields either all patches from an image or N random patches.""" if n_random_patches is None: return blocks_generator(image, block_size, block_overlap) else: - return random_patches(image, block_size, n_random_patches=n_random_patches) + return random_patches( + image, block_size, n_random_patches=n_random_patches + ) def the_giant_video_loader( @@ -416,7 +486,9 @@ def the_giant_video_loader( if region == "whole": generator = iter(pad_sample.data) elif region == "crop": - generator = yield_faces(pad_sample, cropper=cropper, normalizer=normalizer) + generator = yield_faces( + pad_sample, cropper=cropper, normalizer=normalizer + ) else: raise ValueError("Invalid region value: `{}'".format(region)) @@ -436,7 +508,8 @@ def the_giant_video_loader( patch for frame in generator for patch in random_sample( - blocks(frame, block_size, block_overlap), random_patches_per_frame + blocks(frame, block_size, block_overlap), + random_patches_per_frame, ) ) @@ -444,9 +517,13 @@ def the_giant_video_loader( generator = (augment(frame) for frame in generator) if keep_pa_samples is not None and not pad_sample.is_bonafide: - generator = (frame for frame in generator if random.random() < keep_pa_samples) + generator = ( + frame for frame in generator if random.random() < keep_pa_samples + ) if keep_bf_samples is not None and pad_sample.is_bonafide: - generator = (frame for frame in generator if random.random() < keep_bf_samples) + generator = ( + frame for frame in generator if random.random() < keep_bf_samples + ) return generator diff --git a/buildout.cfg b/buildout.cfg index abdf98d139d98e6c4c2f4fd8ff9c9a1b49efdd18..5596f8465ceb0ef01fd4651023f9561d9e1a45d5 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -11,4 +11,4 @@ verbose = true [scripts] recipe = bob.buildout:scripts -dependent-scripts = true \ No newline at end of file +dependent-scripts = true diff --git a/doc/conf.py b/doc/conf.py index 0f7ce4f359b3dacaf5b84eee4fb32ea65215b66d..b241c7e6bb4572841e2a2789aa7a5904a98b88d0 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -2,30 +2,28 @@ # vim: set fileencoding=utf-8 : import os -import sys -import glob + import pkg_resources # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.3' +needs_sphinx = "1.3" # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.ifconfig', - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.doctest', - 'sphinx.ext.graphviz', - 'sphinx.ext.intersphinx', - 'sphinx.ext.napoleon', - 'sphinx.ext.viewcode', - 'sphinx.ext.mathjax', - #'matplotlib.sphinxext.plot_directive' + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.ifconfig", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.doctest", + "sphinx.ext.graphviz", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", + "sphinx.ext.mathjax", ] # Be picky about warnings @@ -35,8 +33,8 @@ nitpicky = False nitpick_ignore = [] # Allows the user to override warnings from a separate file -if os.path.exists('nitpick-exceptions.txt'): - for line in open('nitpick-exceptions.txt'): +if os.path.exists("nitpick-exceptions.txt"): + for line in open("nitpick-exceptions.txt"): if line.strip() == "" or line.startswith("#"): continue dtype, target = line.split(None, 1) @@ -57,25 +55,27 @@ autosummary_generate = True numfig = True # If we are on OSX, the 'dvipng' path maybe different -dvipng_osx = '/opt/local/libexec/texlive/binaries/dvipng' -if os.path.exists(dvipng_osx): pngmath_dvipng = dvipng_osx +dvipng_osx = "/opt/local/libexec/texlive/binaries/dvipng" +if os.path.exists(dvipng_osx): + pngmath_dvipng = dvipng_osx # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'bob.pad.face' +project = u"bob.pad.face" import time -copyright = u'%s, Idiap Research Institute' % time.strftime('%Y') + +copyright = u"%s, Idiap Research Institute" % time.strftime("%Y") # Grab the setup entry distribution = pkg_resources.require(project)[0] @@ -91,122 +91,123 @@ release = distribution.version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['links.rst'] +exclude_patterns = ["links.rst"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # Some variables which are useful for generated material -project_variable = project.replace('.', '_') -short_description = u'Presentation Attack Detection in Face Biometrics' -owner = [u'Idiap Research Institute'] +project_variable = project.replace(".", "_") +short_description = u"Presentation Attack Detection in Face Biometrics" +owner = [u"Idiap Research Institute"] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. import sphinx_rtd_theme -html_theme = 'sphinx_rtd_theme' + +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # The name for this set of Sphinx documents. If None, it defaults to # "<project> v<release> documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = project_variable +# html_short_title = project_variable # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = 'img/logo.png' +html_logo = "img/logo.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -html_favicon = 'img/favicon.ico' +html_favicon = "img/favicon.ico" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] +# html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a <link> tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = project_variable + u'_doc' +htmlhelp_basename = project_variable + u"_doc" # -- Post configuration -------------------------------------------------------- @@ -215,25 +216,27 @@ rst_epilog = """ .. |project| replace:: Bob .. |version| replace:: %s .. |current-year| date:: %%Y -""" % (version, ) +""" % ( + version, +) # Default processing flags for sphinx -autoclass_content = 'class' -autodoc_member_order = 'bysource' +autoclass_content = "class" +autodoc_member_order = "bysource" autodoc_default_options = { - "members": True, - "undoc-members": True, - "show-inheritance": True, + "members": True, + "undoc-members": True, + "show-inheritance": True, } # For inter-documentation mapping: from bob.extension.utils import link_documentation, load_requirements + sphinx_requirements = "extra-intersphinx.txt" if os.path.exists(sphinx_requirements): intersphinx_mapping = link_documentation( - additional_packages=['python','numpy'] + \ - load_requirements(sphinx_requirements) - ) + additional_packages=["python", "numpy"] + + load_requirements(sphinx_requirements) + ) else: intersphinx_mapping = link_documentation() - diff --git a/doc/index.rst b/doc/index.rst index ba3762b8a171e013731da184bd44a6b84701d709..27fc0ff46935ee8c2e3da4246b9e8ec76f5e14ec 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -29,5 +29,3 @@ Users Guide .. todolist:: .. include:: links.rst - - diff --git a/doc/other_pad_algorithms.rst b/doc/other_pad_algorithms.rst index 4aa4348203e6b865bb98794e4ab6353b9ee7d6a8..6e433971df6f9d4573901b33257ff2f2fd774229 100644 --- a/doc/other_pad_algorithms.rst +++ b/doc/other_pad_algorithms.rst @@ -40,7 +40,7 @@ Usually, it is a good idea to have at least verbose level 2 (i.e., calling To run the experiments in parallel, you can use an existing or (define a new) SGE grid or local host multiprocessing configuration. To run the experiment in the Idiap SGE grid, you can simply add the ``--dask-client sge`` command - line option. To run experiments in parallel on the local machine, add the + line option. To run experiments in parallel on the local machine, add the ``--dask-client local-parallel`` option. See :any:`this <pipeline_simple_features>` for more @@ -54,5 +54,3 @@ is available on the section :ref:`bob.pad.face.resources`. .. include:: links.rst - - diff --git a/doc/resources.rst b/doc/resources.rst index ed25618dcd5e5db2c0f4576e30ab50bae4e7eeae..b988ac996550c8171dfff131b10eb0024b76eb43 100644 --- a/doc/resources.rst +++ b/doc/resources.rst @@ -57,4 +57,3 @@ The configuration files contain at least the following arguments of the ``bob pad vanilla-pad`` command: * ``pipeline`` containing zero, one, or more Transformers and one Classifier - diff --git a/pyproject.toml b/pyproject.toml index bb5e83cb40b5871b321beb790ae14606b010e4ca..b738dc847ff9705c5769673db7415f2eb9a75f4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,12 @@ [build-system] -requires = ["setuptools", "wheel", "bob.extension"] -build-backend = "setuptools.build_meta" + requires = ["setuptools", "wheel", "bob.extension"] + build-backend = "setuptools.build_meta" + +[tool.isort] + profile = "black" + line_length = 80 + order_by_type = true + lines_between_types = 1 + +[tool.black] + line-length = 80 diff --git a/setup.py b/setup.py index c970d6d5eb5376f6ed49600f98c62fa899012e4f..a9296ae6020248aa2f31556173defa9716fce51f 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,12 @@ #!/usr/bin/env python # vim: set fileencoding=utf-8 : -from setuptools import setup, dist +from setuptools import dist, setup dist.Distribution(dict(setup_requires=["bob.extension"])) # load the requirements.txt for additional requirements -from bob.extension.utils import load_requirements, find_packages +from bob.extension.utils import find_packages, load_requirements install_requires = load_requirements() @@ -52,8 +52,7 @@ setup( # the version of bob. entry_points={ # scripts should be declared using this entry: - "console_scripts": [ - ], + "console_scripts": [], # registered databases: "bob.pad.database": [ "replay-attack = bob.pad.face.config.replay_attack:database",