Skip to content
Snippets Groups Projects
Commit 6c7e025b authored by Amir MOHAMMADI's avatar Amir MOHAMMADI
Browse files

report the matplotlib backend if scatter fails

parent de9a8fec
No related branches found
No related tags found
No related merge requests found
Pipeline #49354 passed
...@@ -76,7 +76,7 @@ class GMM(AlgorithmBob): ...@@ -76,7 +76,7 @@ class GMM(AlgorithmBob):
if self.gaussians is None: if self.gaussians is None:
self.gaussians = array.shape[1] + 1 self.gaussians = array.shape[1] + 1
logger.warn("Number of Gaussians was None. " logger.warning("Number of Gaussians was None. "
"Using {}.".format(self.gaussians)) "Using {}.".format(self.gaussians))
# Computes input size # Computes input size
......
"""Plots the decision boundaries of fusion algorithms. """Plots the decision boundaries of fusion algorithms.
""" """
from __future__ import print_function, absolute_import, division
import os
import logging import logging
import click import click
from bob.extension.scripts.click_helper import verbosity_option from bob.extension.scripts.click_helper import verbosity_option
...@@ -10,173 +8,243 @@ import numpy as np ...@@ -10,173 +8,243 @@ import numpy as np
from bob.bio.base.score import load_score from bob.bio.base.score import load_score
from ..tools import ( from ..tools import (
get_gza_from_lines_list, check_consistency, get_scores, remove_nan, get_gza_from_lines_list,
grouping) check_consistency,
get_scores,
remove_nan,
grouping,
)
from ..algorithm import Algorithm from ..algorithm import Algorithm
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def plot_boundary_decision(algorithm, scores, score_labels, threshold, def plot_boundary_decision(
thres_system1=None, algorithm,
thres_system2=None, scores,
do_grouping=False, score_labels,
resolution=2000, threshold,
alpha=0.75, thres_system1=None,
legends=None, thres_system2=None,
i1=0, do_grouping=False,
i2=1, resolution=2000,
x_label=None, alpha=0.75,
y_label=None, legends=None,
**kwargs i1=0,
): i2=1,
if legends is None: x_label=None,
legends = ['Zero Effort Impostor', 'Presentation Attack', 'Genuine'] y_label=None,
markers = ['x', 'o', 's'] **kwargs,
):
if scores.shape[1] > 2: if legends is None:
raise NotImplementedError( legends = ["Zero Effort Impostor", "Presentation Attack", "Genuine"]
"Currently plotting the decision boundary for more than two " markers = ["x", "o", "s"]
"systems is not supported.")
if scores.shape[1] > 2:
import matplotlib.pyplot as plt raise NotImplementedError(
plt.gca() # this is necessary for subplots to work. "Currently plotting the decision boundary for more than two "
"systems is not supported."
X = scores[:, [i1, i2]] )
Y = score_labels
x_pad = (X[:, i1].max() - X[:, i1].min()) * 0.1 import matplotlib
y_pad = (X[:, i2].max() - X[:, i2].min()) * 0.1 import matplotlib.pyplot as plt
x_min, x_max = X[:, i1].min() - x_pad, X[:, i1].max() + x_pad
y_min, y_max = X[:, i2].min() - y_pad, X[:, i2].max() + y_pad plt.gca() # this is necessary for subplots to work.
xx, yy = np.meshgrid(
np.linspace(x_min, x_max, resolution), X = scores[:, [i1, i2]]
np.linspace(y_min, y_max, resolution)) Y = score_labels
x_pad = (X[:, i1].max() - X[:, i1].min()) * 0.1
contourf = None y_pad = (X[:, i2].max() - X[:, i2].min()) * 0.1
if algorithm is not None: x_min, x_max = X[:, i1].min() - x_pad, X[:, i1].max() + x_pad
temp = np.c_[xx.ravel(), yy.ravel()] y_min, y_max = X[:, i2].min() - y_pad, X[:, i2].max() + y_pad
temp = algorithm.preprocess(temp) xx, yy = np.meshgrid(
Z = (algorithm.fuse(temp) > threshold).reshape(xx.shape) np.linspace(x_min, x_max, resolution), np.linspace(y_min, y_max, resolution)
)
contourf = plt.contour(xx, yy, Z, 1, alpha=1, cmap=plt.cm.gray)
contourf = None
if do_grouping: if algorithm is not None:
gen = grouping(X[Y == 0, :], **kwargs) temp = np.c_[xx.ravel(), yy.ravel()]
zei = grouping(X[Y == 1, :], **kwargs) temp = algorithm.preprocess(temp)
atk = grouping(X[Y == 2, :], **kwargs) Z = (algorithm.fuse(temp) > threshold).reshape(xx.shape)
else:
gen = X[Y == 0, :] contourf = plt.contour(xx, yy, Z, 1, alpha=1, cmap=plt.cm.gray)
zei = X[Y == 1, :]
atk = X[Y == 2, :] if do_grouping:
for i, (X, color) in enumerate(((zei, 'C0'), (atk, 'C1'), (gen, 'C2'))): gen = grouping(X[Y == 0, :], **kwargs)
if X.size == 0: zei = grouping(X[Y == 1, :], **kwargs)
continue atk = grouping(X[Y == 2, :], **kwargs)
plt.scatter( else:
X[:, 0], X[:, 1], marker=markers[i], alpha=alpha, gen = X[Y == 0, :]
c=color, label=legends[i]) zei = X[Y == 1, :]
plt.legend(bbox_to_anchor=(-0.05, 1.02, 1.05, .102), loc=3, atk = X[Y == 2, :]
ncol=3, mode="expand", borderaxespad=0., fontsize=14) for i, (X, color) in enumerate(((zei, "C0"), (atk, "C1"), (gen, "C2"))):
if X.size == 0:
if thres_system1 is not None: continue
plt.axvline(thres_system1, color='red') try:
plt.axhline(thres_system2, color='red') plt.scatter(
X[:, 0], X[:, 1], marker=markers[i], alpha=alpha, c=color, label=legends[i]
plt.xlim([x_min, x_max]) )
plt.ylim([y_min, y_max]) except Exception as e:
plt.grid(True) raise RuntimeError(f"matplotlib backend: {matplotlib.get_backend()}") from e
plt.xlabel(x_label) plt.legend(
plt.ylabel(y_label) bbox_to_anchor=(-0.05, 1.02, 1.05, 0.102),
loc=3,
return contourf ncol=3,
mode="expand",
borderaxespad=0.0,
@click.command(epilog="""\b fontsize=14,
)
if thres_system1 is not None:
plt.axvline(thres_system1, color="red")
plt.axhline(thres_system2, color="red")
plt.xlim([x_min, x_max])
plt.ylim([y_min, y_max])
plt.grid(True)
plt.xlabel(x_label)
plt.ylabel(y_label)
return contourf
@click.command(
epilog="""\b
Examples: Examples:
$ bob fusion boundary -vvv {sys1,sys2}/scores-eval -m /path/to/Model.pkl $ bob fusion boundary -vvv {sys1,sys2}/scores-eval -m /path/to/Model.pkl
""") """
@click.argument('scores', nargs=-1, required=True, )
type=click.Path(exists=True)) @click.argument("scores", nargs=-1, required=True, type=click.Path(exists=True))
@click.option('-m', '--model-file', required=False, @click.option(
help='The path to where the algorithm will be loaded from.') "-m",
@click.option('-t', '--threshold', type=click.FLOAT, required=False, "--model-file",
help='The threshold to classify scores after fusion. Usually ' required=False,
'calculated from fused development set.') help="The path to where the algorithm will be loaded from.",
@click.option('-g', '--group', type=click.INT, default=0, show_default=True, )
help='If given scores will be grouped into N samples.') @click.option(
@click.option('-G', '--grouping', type=click.Choice(('random', 'kmeans')), "-t",
default='kmeans', show_default=True, "--threshold",
help='The gouping algorithm to be used.') type=click.FLOAT,
@click.option('-o', '--output', default='scatter.pdf', required=False,
show_default=True, type=click.Path(writable=True), help="The threshold to classify scores after fusion. Usually "
help='The path to the saved plot.') "calculated from fused development set.",
@click.option('-X', '--x-label', default='Recognition scores', )
show_default=True, help='The label for the first system.') @click.option(
@click.option('-Y', '--y-label', default='PAD scores', show_default=True, "-g",
help='The label for the second system.') "--group",
@click.option('--skip-check', is_flag=True, show_default=True, type=click.INT,
help='If True, it will skip checking for the consistency ' default=0,
'between scores.') show_default=True,
help="If given scores will be grouped into N samples.",
)
@click.option(
"-G",
"--grouping",
type=click.Choice(("random", "kmeans")),
default="kmeans",
show_default=True,
help="The gouping algorithm to be used.",
)
@click.option(
"-o",
"--output",
default="scatter.pdf",
show_default=True,
type=click.Path(writable=True),
help="The path to the saved plot.",
)
@click.option(
"-X",
"--x-label",
default="Recognition scores",
show_default=True,
help="The label for the first system.",
)
@click.option(
"-Y",
"--y-label",
default="PAD scores",
show_default=True,
help="The label for the second system.",
)
@click.option(
"--skip-check",
is_flag=True,
show_default=True,
help="If True, it will skip checking for the consistency " "between scores.",
)
@verbosity_option() @verbosity_option()
def boundary(scores, model_file, threshold, group, grouping, output, x_label, def boundary(
y_label, skip_check, **kwargs): scores,
"""Plots the decision boundaries of fusion algorithms. model_file,
threshold,
The script takes several scores (usually eval scores) from different group,
biometric and pad systems and a trained algorithm and plots the decision grouping,
boundary. output,
x_label,
You need to provide two score files from two systems. System 1 will be y_label,
plotted on the x-axis. skip_check,
""" **kwargs,
# load the algorithm ):
algorithm = None """Plots the decision boundaries of fusion algorithms.
if model_file:
algorithm = Algorithm().load(model_file) The script takes several scores (usually eval scores) from different
assert threshold is not None, "threshold must be provided with the model" biometric and pad systems and a trained algorithm and plots the decision
boundary.
# load the scores
score_lines_list_eval = [load_score(path) for path in scores] You need to provide two score files from two systems. System 1 will be
plotted on the x-axis.
# genuine, zero effort impostor, and attack list """
idx1, gen_le, zei_le, atk_le = get_gza_from_lines_list( # load the algorithm
score_lines_list_eval) algorithm = None
if model_file:
# check if score lines are consistent algorithm = Algorithm().load(model_file)
if not skip_check: assert threshold is not None, "threshold must be provided with the model"
check_consistency(gen_le, zei_le, atk_le)
# load the scores
# concatenate the scores and create the labels score_lines_list_eval = [load_score(path) for path in scores]
scores = get_scores(gen_le, zei_le, atk_le)
score_labels = np.zeros((scores.shape[0],)) # genuine, zero effort impostor, and attack list
gensize = gen_le[0].shape[0] idx1, gen_le, zei_le, atk_le = get_gza_from_lines_list(score_lines_list_eval)
zeisize = zei_le[0].shape[0]
score_labels[:gensize] = 0 # check if score lines are consistent
score_labels[gensize: gensize + zeisize] = 1 if not skip_check:
score_labels[gensize + zeisize:] = 2 check_consistency(gen_le, zei_le, atk_le)
found_nan, nan_idx, scores = remove_nan(scores, False)
score_labels = score_labels[~nan_idx] # concatenate the scores and create the labels
scores = get_scores(gen_le, zei_le, atk_le)
if found_nan: score_labels = np.zeros((scores.shape[0],))
logger.warn('{} nan values were removed.'.format(np.sum(nan_idx))) gensize = gen_le[0].shape[0]
zeisize = zei_le[0].shape[0]
# plot the decision boundary score_labels[:gensize] = 0
do_grouping = True score_labels[gensize : gensize + zeisize] = 1
if group < 1: score_labels[gensize + zeisize :] = 2
do_grouping = False found_nan, nan_idx, scores = remove_nan(scores, False)
score_labels = score_labels[~nan_idx]
import matplotlib
if not hasattr(matplotlib, 'backends'): if found_nan:
matplotlib.use('pdf') logger.warn("{} nan values were removed.".format(np.sum(nan_idx)))
import matplotlib.pyplot as plt
plot_boundary_decision( # plot the decision boundary
algorithm, scores, score_labels, threshold, do_grouping = True
do_grouping=do_grouping, if group < 1:
npoints=group, do_grouping = False
seed=0,
gformat=grouping, import matplotlib.pyplot as plt
x_label=x_label,
y_label=y_label, plot_boundary_decision(
) algorithm,
plt.savefig(output, transparent=True) scores,
plt.close() score_labels,
threshold,
do_grouping=do_grouping,
npoints=group,
seed=0,
gformat=grouping,
x_label=x_label,
y_label=y_label,
)
plt.savefig(output, transparent=True)
plt.close()
...@@ -8,7 +8,6 @@ from bob.extension.scripts.click_helper import ( ...@@ -8,7 +8,6 @@ from bob.extension.scripts.click_helper import (
import os import os
import numpy as np import numpy as np
from bob.io.base import create_directories_safe
from bob.bio.base.score import load_score, dump_score from bob.bio.base.score import load_score, dump_score
from bob.bio.base import utils from bob.bio.base import utils
...@@ -45,7 +44,7 @@ kwargs: %s ...@@ -45,7 +44,7 @@ kwargs: %s
def save_fused_scores(save_path, fused_scores, score_lines): def save_fused_scores(save_path, fused_scores, score_lines):
score_lines['score'] = fused_scores score_lines['score'] = fused_scores
gen, zei, atk, _, _, _ = get_2negatives_1positive(score_lines) gen, zei, atk, _, _, _ = get_2negatives_1positive(score_lines)
create_directories_safe(os.path.dirname(save_path)) os.makedirs(os.path.dirname(save_path), exist_ok=True)
dump_score(save_path, score_lines) dump_score(save_path, score_lines)
dump_score(save_path + '-licit', np.append(gen, zei)) dump_score(save_path + '-licit', np.append(gen, zei))
dump_score(save_path + '-spoof', np.append(gen, atk)) dump_score(save_path + '-spoof', np.append(gen, atk))
...@@ -190,7 +189,7 @@ def fuse(scores, algorithm, groups, output_dir, model_file, skip_check, force, ...@@ -190,7 +189,7 @@ def fuse(scores, algorithm, groups, output_dir, model_file, skip_check, force,
click.MissingParameter click.MissingParameter
If the algorithm is not provided. If the algorithm is not provided.
""" """
create_directories_safe(output_dir) os.makedirs(output_dir, exist_ok=True)
if not model_file: if not model_file:
do_training = True do_training = True
model_file = os.path.join(output_dir, 'Model.pkl') model_file = os.path.join(output_dir, 'Model.pkl')
...@@ -303,7 +302,7 @@ def fuse(scores, algorithm, groups, output_dir, model_file, skip_check, force, ...@@ -303,7 +302,7 @@ def fuse(scores, algorithm, groups, output_dir, model_file, skip_check, force,
scores_eval_lines = scores_eval_lines[~nan_eval] scores_eval_lines = scores_eval_lines[~nan_eval]
if found_nan: if found_nan:
logger.warn('Some nan values were removed.') logger.warning('Some nan values were removed.')
routine_fusion( routine_fusion(
algorithm, model_file, scores_train_lines, scores_train, algorithm, model_file, scores_train_lines, scores_train,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment