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

1"""Gets statistics on the average face size in a video database. 

2""" 

3import logging 

4 

5from os.path import expanduser 

6 

7import click 

8import numpy as np 

9 

10from clapper.click import ConfigCommand, ResourceOption, verbosity_option 

11 

12from bob.bio.face.annotator import ( 

13 BoundingBox, 

14 bounding_box_from_annotation, 

15 expected_eye_positions, 

16) 

17 

18logger = logging.getLogger(__name__) 

19BF = "Bona Fide" 

20PA = "Presentation Attack" 

21 

22 

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. 

40 

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). 

54 

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) 

67 

68 # Some databases need their original_directory to be replaced 

69 database.replace_directories(database_directories_file) 

70 

71 biofiles = database.objects(groups=None, protocol=database.protocol) 

72 biofiles = sorted(biofiles) 

73 

74 logger.info("Gathering face size statistics of %d samples", len(biofiles)) 

75 

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) 

94 

95 if output: 

96 import matplotlib.pyplot as plt 

97 

98 # from matplotlib.backends.backend_pdf import PdfPages 

99 # pp = PdfPages(output) 

100 

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))) 

127 

128 if not output: 

129 continue 

130 

131 # plot the face sizes 

132 

133 # plt.hist2d(face_sizes[:, 1], face_sizes[:, 0], bins=500) 

134 # plt.xlabel('Width') 

135 # plt.ylabel('Height') 

136 # plt.grid() 

137 

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) 

148 

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") 

159 

160 plt.tight_layout() 

161 plt.legend() 

162 plt.savefig(output) 

163 # pp.savefig(plt.gcf())