Coverage for src/bob/fusion/base/script/boundary.py: 92%
92 statements
« prev ^ index » next coverage.py v7.6.5, created at 2024-11-14 22:15 +0100
« prev ^ index » next coverage.py v7.6.5, created at 2024-11-14 22:15 +0100
1"""Plots the decision boundaries of fusion algorithms.
2"""
3import logging
5import click
6import numpy as np
8from clapper.click import verbosity_option
10from bob.bio.base.score import load_score
12from ..algorithm import Algorithm
13from ..tools import (
14 check_consistency,
15 get_gza_from_lines_list,
16 get_scores,
17 grouping,
18 remove_nan,
19)
21logger = logging.getLogger(__name__)
24def plot_boundary_decision(
25 algorithm,
26 scores,
27 score_labels,
28 threshold,
29 thres_system1=None,
30 thres_system2=None,
31 do_grouping=False,
32 resolution=2000,
33 alpha=0.75,
34 legends=None,
35 i1=0,
36 i2=1,
37 x_label=None,
38 y_label=None,
39 **kwargs,
40):
41 if legends is None:
42 legends = ["Zero Effort Impostor", "Presentation Attack", "Genuine"]
43 markers = ["x", "o", "s"]
45 if scores.shape[1] > 2:
46 raise NotImplementedError(
47 "Currently plotting the decision boundary for more than two "
48 "systems is not supported."
49 )
51 import matplotlib
52 import matplotlib.pyplot as plt
54 plt.gca() # this is necessary for subplots to work.
56 X = scores[:, [i1, i2]]
57 Y = score_labels
58 x_pad = (X[:, i1].max() - X[:, i1].min()) * 0.1
59 y_pad = (X[:, i2].max() - X[:, i2].min()) * 0.1
60 x_min, x_max = X[:, i1].min() - x_pad, X[:, i1].max() + x_pad
61 y_min, y_max = X[:, i2].min() - y_pad, X[:, i2].max() + y_pad
62 xx, yy = np.meshgrid(
63 np.linspace(x_min, x_max, resolution),
64 np.linspace(y_min, y_max, resolution),
65 )
67 contourf = None
68 if algorithm is not None:
69 temp = np.c_[xx.ravel(), yy.ravel()]
70 temp = algorithm.preprocess(temp)
71 Z = (algorithm.fuse(temp) > threshold).reshape(xx.shape)
73 contourf = plt.contour(xx, yy, Z, 1, alpha=1, cmap=plt.cm.gray)
75 if do_grouping:
76 gen = grouping(X[Y == 0, :], **kwargs)
77 zei = grouping(X[Y == 1, :], **kwargs)
78 atk = grouping(X[Y == 2, :], **kwargs)
79 else:
80 gen = X[Y == 0, :]
81 zei = X[Y == 1, :]
82 atk = X[Y == 2, :]
83 for i, (X, color) in enumerate(((zei, "C0"), (atk, "C1"), (gen, "C2"))):
84 if X.size == 0:
85 continue
86 try:
87 plt.scatter(
88 X[:, 0],
89 X[:, 1],
90 marker=markers[i],
91 alpha=alpha,
92 c=color,
93 label=legends[i],
94 )
95 except Exception as e:
96 raise RuntimeError(
97 f"matplotlib backend: {matplotlib.get_backend()}"
98 ) from e
100 plt.legend(
101 bbox_to_anchor=(-0.05, 1.02, 1.05, 0.102),
102 loc=3,
103 ncol=3,
104 mode="expand",
105 borderaxespad=0.0,
106 fontsize=14,
107 )
109 if thres_system1 is not None:
110 plt.axvline(thres_system1, color="red")
111 plt.axhline(thres_system2, color="red")
113 plt.xlim([x_min, x_max])
114 plt.ylim([y_min, y_max])
115 plt.grid(True)
117 plt.xlabel(x_label)
118 plt.ylabel(y_label)
120 return contourf
123@click.command(
124 epilog="""\b
125Examples:
126$ bob fusion boundary -vvv {sys1,sys2}/scores-eval -m /path/to/Model.pkl
127"""
128)
129@click.argument("scores", nargs=-1, required=True, type=click.Path(exists=True))
130@click.option(
131 "-m",
132 "--model-file",
133 required=False,
134 help="The path to where the algorithm will be loaded from.",
135)
136@click.option(
137 "-t",
138 "--threshold",
139 type=click.FLOAT,
140 required=False,
141 help="The threshold to classify scores after fusion. Usually "
142 "calculated from fused development set.",
143)
144@click.option(
145 "-g",
146 "--group",
147 type=click.INT,
148 default=0,
149 show_default=True,
150 help="If given scores will be grouped into N samples.",
151)
152@click.option(
153 "-G",
154 "--grouping",
155 type=click.Choice(("random", "kmeans")),
156 default="kmeans",
157 show_default=True,
158 help="The gouping algorithm to be used.",
159)
160@click.option(
161 "-o",
162 "--output",
163 default="scatter.pdf",
164 show_default=True,
165 type=click.Path(writable=True),
166 help="The path to the saved plot.",
167)
168@click.option(
169 "-X",
170 "--x-label",
171 default="Recognition scores",
172 show_default=True,
173 help="The label for the first system.",
174)
175@click.option(
176 "-Y",
177 "--y-label",
178 default="PAD scores",
179 show_default=True,
180 help="The label for the second system.",
181)
182@click.option(
183 "--skip-check",
184 is_flag=True,
185 show_default=True,
186 help="If True, it will skip checking for the consistency "
187 "between scores.",
188)
189@verbosity_option(logger)
190def boundary(
191 scores,
192 model_file,
193 threshold,
194 group,
195 grouping,
196 output,
197 x_label,
198 y_label,
199 skip_check,
200 **kwargs,
201):
202 """Plots the decision boundaries of fusion algorithms.
204 The script takes several scores (usually eval scores) from different
205 biometric and pad systems and a trained algorithm and plots the decision
206 boundary.
208 You need to provide two score files from two systems. System 1 will be
209 plotted on the x-axis.
210 """
211 # load the algorithm
212 algorithm = None
213 if model_file:
214 algorithm = Algorithm().load(model_file)
215 assert (
216 threshold is not None
217 ), "threshold must be provided with the model"
219 # load the scores
220 score_lines_list_eval = [load_score(path) for path in scores]
222 # genuine, zero effort impostor, and attack list
223 idx1, gen_le, zei_le, atk_le = get_gza_from_lines_list(
224 score_lines_list_eval
225 )
227 # check if score lines are consistent
228 if not skip_check:
229 check_consistency(gen_le, zei_le, atk_le)
231 # concatenate the scores and create the labels
232 scores = get_scores(gen_le, zei_le, atk_le)
233 score_labels = np.zeros((scores.shape[0],))
234 gensize = gen_le[0].shape[0]
235 zeisize = zei_le[0].shape[0]
236 score_labels[:gensize] = 0
237 score_labels[gensize : gensize + zeisize] = 1
238 score_labels[gensize + zeisize :] = 2
239 found_nan, nan_idx, scores = remove_nan(scores, False)
240 score_labels = score_labels[~nan_idx]
242 if found_nan:
243 logger.warn("{} nan values were removed.".format(np.sum(nan_idx)))
245 # plot the decision boundary
246 do_grouping = True
247 if group < 1:
248 do_grouping = False
250 import matplotlib.pyplot as plt
252 plot_boundary_decision(
253 algorithm,
254 scores,
255 score_labels,
256 threshold,
257 do_grouping=do_grouping,
258 npoints=group,
259 seed=0,
260 gformat=grouping,
261 x_label=x_label,
262 y_label=y_label,
263 )
264 plt.savefig(output, transparent=True)
265 plt.close()