Coverage for src/bob/pad/face/script/statistics.py: 0%
58 statements
« prev ^ index » next coverage.py v7.6.0, created at 2024-07-13 01:19 +0200
« prev ^ index » next coverage.py v7.6.0, created at 2024-07-13 01:19 +0200
1"""Gets statistics on the average face size in a video database.
2"""
3import logging
5from os.path import expanduser
7import click
8import numpy as np
10from clapper.click import ConfigCommand, ResourceOption, verbosity_option
12from bob.bio.face.annotator import (
13 BoundingBox,
14 bounding_box_from_annotation,
15 expected_eye_positions,
16)
18logger = logging.getLogger(__name__)
19BF = "Bona Fide"
20PA = "Presentation Attack"
23@click.command(entry_point_group="bob.bio.config", cls=ConfigCommand)
24@click.option(
25 "--database",
26 "-d",
27 required=True,
28 cls=ResourceOption,
29 entry_point_group="bob.pad.database",
30)
31@click.option("-o", "--output", default="face_sizes.png")
32@click.option(
33 "--database-directories-file",
34 cls=ResourceOption,
35 default=expanduser("~/.bob_bio_databases.txt"),
36)
37@verbosity_option(logger=logger, expose_value=False)
38def statistics(database, output, database_directories_file, **kwargs):
39 """Statistics on face size in video databases.
41 \b
42 Parameters
43 ----------
44 database : :any:`bob.pad.database`
45 The database that you want to annotate. Can be a ``bob.pad.database``
46 entry point or a path to a Python file which contains a variable
47 named `database`.
48 output : str
49 Path to the saved figure.
50 database_directories_file : str
51 Path to a custom ``~/.bob_bio_databases.txt`` file.
52 verbose : int, optional
53 Increases verbosity (see help for --verbose).
55 \b
56 [CONFIG]... Configuration files. It is possible to pass one or
57 several Python files (or names of ``bob.bio.config``
58 entry points) which contain the parameters listed
59 above as Python variables. The options through the
60 command-line (see below) will override the values of
61 configuration files.
62 """
63 logger.debug("database: %s", database)
64 logger.debug("output: %s", output)
65 logger.debug("database_directories_file: %s", database_directories_file)
66 logger.debug("kwargs: %s", kwargs)
68 # Some databases need their original_directory to be replaced
69 database.replace_directories(database_directories_file)
71 biofiles = database.objects(groups=None, protocol=database.protocol)
72 biofiles = sorted(biofiles)
74 logger.info("Gathering face size statistics of %d samples", len(biofiles))
76 face_sizes_dict = {BF: [], PA: []}
77 for i, biofile in enumerate(biofiles):
78 for annot in database.annotations(biofile).values():
79 # get the bounding box
80 for source in ("direct", "eyes", None):
81 try:
82 bbx = bounding_box_from_annotation(source=source, **annot)
83 break
84 except Exception:
85 if source is None:
86 raise
87 else:
88 pass
89 # record face size
90 if biofile.attack_type is None:
91 face_sizes_dict[BF].append(bbx.size)
92 else:
93 face_sizes_dict[PA].append(bbx.size)
95 if output:
96 import matplotlib.pyplot as plt
98 # from matplotlib.backends.backend_pdf import PdfPages
99 # pp = PdfPages(output)
101 for attack_type, face_sizes in face_sizes_dict.items():
102 click.echo(attack_type)
103 face_sizes = np.array(face_sizes)
104 # get statistics on the face sizes
105 for name, array in (
106 ("Height", face_sizes[:, 0]),
107 ("Width", face_sizes[:, 1]),
108 ):
109 click.echo(
110 "min: {}, mean: {}, max: {}, std: {:.1f} for {}".format(
111 array.min(),
112 int(array.mean()),
113 array.max(),
114 array.std(),
115 name,
116 )
117 )
118 # print the average eye distance assuming bounding boxes are from
119 # bob.ip.facedetect or the annotations had eye locations in them
120 bbx = BoundingBox((0, 0), face_sizes.mean(axis=0))
121 annot = expected_eye_positions(bbx)
122 eye_distance = np.linalg.norm(
123 np.array(annot["reye"]) - np.array(annot["leye"])
124 )
125 click.echo("Average eye locations: {}".format(annot))
126 click.echo("Average eye distance: {}".format(int(eye_distance)))
128 if not output:
129 continue
131 # plot the face sizes
133 # plt.hist2d(face_sizes[:, 1], face_sizes[:, 0], bins=500)
134 # plt.xlabel('Width')
135 # plt.ylabel('Height')
136 # plt.grid()
138 # from matplotlib import cm
139 # from mpl_toolkits.mplot3d import Axes3D
140 # Z, xedges, yedges, _ = plt.hist2d(
141 # face_sizes[:, 1], face_sizes[:, 0], bins=500, normed=True)
142 # xcenters = (xedges[:-1] + xedges[1:]) / 2
143 # ycenters = (yedges[:-1] + yedges[1:]) / 2
144 # X, Y = np.meshgrid(xcenters, ycenters)
145 # fig = plt.figure()
146 # ax = Axes3D(fig)
147 # ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.viridis)
149 plt.hist(
150 face_sizes[:, 1],
151 density=True,
152 bins="auto",
153 label=attack_type,
154 alpha=0.5,
155 )
156 if output:
157 plt.xlabel("Width of faces")
158 plt.ylabel("Probability Density")
160 plt.tight_layout()
161 plt.legend()
162 plt.savefig(output)
163 # pp.savefig(plt.gcf())