From dcc722963921743aad33e73449211ab3ef9afef1 Mon Sep 17 00:00:00 2001 From: Theophile GENTILHOMME <tgentilhomme@jurasix08.idiap.ch> Date: Mon, 9 Apr 2018 12:06:50 +0200 Subject: [PATCH] Add evaluate command --- bob/bio/base/script/commands.py | 180 ++++++++++++++++++++++++++++- bob/bio/base/test/test_commands.py | 64 ++++++++++ setup.py | 4 +- 3 files changed, 243 insertions(+), 5 deletions(-) diff --git a/bob/bio/base/script/commands.py b/bob/bio/base/script/commands.py index 6a205222..f97833e3 100644 --- a/bob/bio/base/script/commands.py +++ b/bob/bio/base/script/commands.py @@ -23,7 +23,7 @@ FUNC_CMC = lambda x: load.load_files(x, load.cmc) @common_options.far_option() @verbosity_option() @click.pass_context -def metrics(ctx, scores, criter, test, **kargs): +def metrics(ctx, scores, test, **kargs): """Prints a single output line that contains all info for a given criterion (eer or hter). @@ -44,7 +44,7 @@ def metrics(ctx, scores, criter, test, **kargs): $ bob bio metrics --test {dev,test}-scores1 {dev,test}-scores2 """ - if criter == 'rr': + if 'criter' in ctx.meta and ctx.meta['criter'] == 'rr': process = bio_figure.Metrics(ctx, scores, test, FUNC_CMC) else: process = bio_figure.Metrics(ctx, scores, test, FUNC_SPLIT) @@ -189,7 +189,6 @@ def hist(ctx, scores, test, **kwargs): @common_options.axes_val_option(dflt=None) @common_options.axis_fontsize_option() @common_options.x_rotation_option() -@common_options.fmr_line_at_option() @verbosity_option() @click.pass_context def cmc(ctx, scores, test, **kargs): @@ -263,3 +262,178 @@ def dic(ctx, scores, test, **kargs): """ process = bio_figure.Dic(ctx, scores, test, FUNC_CMC) process.run() + +@click.command() +@common_options.scores_argument(nargs=-1) +@common_options.output_plot_file_option(default_out='hist.pdf') +@common_options.test_option() +@common_options.n_bins_option() +@common_options.criterion_option() +@common_options.axis_fontsize_option() +@common_options.thresholds_option() +@verbosity_option() +@click.pass_context +def hist(ctx, scores, test, **kwargs): + """ Plots histograms of positive and negatives along with threshold + criterion. + + You need provide one or more development score file(s) for each experiment. + You can also provide test files along with dev files but the flag `--test` + is required in that case. + + Examples: + $ bob bio hist dev-scores + + $ bob bio hist --test dev-scores1 test-scores1 dev-scores2 + test-scores2 + + $ bob bio hist --test --criter hter dev-scores1 test-scores1 + """ + process = measure_figure.Hist(ctx, scores, test, FUNC_SPLIT) + process.run() + +@click.command() +@common_options.scores_argument(nargs=-1) +@common_options.titles_option() +@common_options.sep_dev_test_option() +@common_options.table_option() +@common_options.test_option() +@common_options.output_plot_metric_option() +@common_options.output_plot_file_option(default_out='eval_plots.pdf') +@common_options.points_curve_option() +@common_options.fmr_line_at_option() +@common_options.cost_option() +@common_options.rank_option() +@common_options.cmc_option() +@common_options.bool_option( + 'metrics', 'M', 'If set, computes table of threshold with EER, HTER (and ' + 'FAR, if ``--far-value`` provided.)' +) +@common_options.far_option() +@common_options.bool_option( + 'cllr', 'x', 'If given, Cllr and minCllr will be computed.' +) +@common_options.bool_option( + 'mindcf', 'm', 'If given, minDCF will be computed.' +) +@common_options.bool_option( + 'rr', 'r', 'If given, the Recognition Rate will be computed.' +) +@common_options.bool_option( + 'hist', 'H', 'If given, score histograms will be generated.' +) +@common_options.bool_option( + 'roc', 'R', 'If given, ROC will be generated.' +) +@common_options.bool_option( + 'det', 'D', 'If given, DET will be generated.' +) +@common_options.bool_option( + 'epc', 'E', 'If given, EPC will be generated.' +) +@common_options.bool_option( + 'dic', 'O', 'If given, DIC will be generated.' +) +@verbosity_option() +@click.pass_context +def evaluate(ctx, scores, test, **kwargs): + '''Evalutes score file, runs error analysis on score sets and plot curves. + + \b + 1. Computes the threshold using either EER, min. HTER or FAR value + criteria on development set scores + 2. Applies the above threshold on test set scores to compute the HTER, if a + test-score set is provided + 3. Computes Cllr and minCllr, minDCF, and recognition rate (if cmc scores + provided) + 3. Reports error metrics in the console or in a log file + 4. Plots ROC, EPC, DET, score distributions, CMC (if cmc) and DIC (if cmc) + curves to a multi-page PDF file + + + You need to provide 2 score files for each biometric system in this order: + + \b + * development scores + * evaluation scores + + Examples: + $ bob bio evaluate dev-scores + + $ bob bio evaluate -t -l metrics.txt -o my_plots.pdf dev-scores test-scores + ''' + log_str='' + if 'log' in ctx.meta and ctx.meta['log'] is not None: + log_str = ' %s' % ctx.meta['log'] + + if ctx.meta['metrics']: + # first time erase if existing file + ctx.meta['open_mode'] = 'w' + click.echo("Computing metrics with EER%s..." % log_str) + ctx.meta['criter'] = 'eer' # no criterion passed to evaluate + ctx.invoke(metrics, scores=scores, test=test) + # other times, appends the content + ctx.meta['open_mode'] = 'a' + click.echo("Computing metrics with HTER%s..." % log_str) + ctx.meta['criter'] = 'hter' # no criterion passed in evaluate + ctx.invoke(metrics, scores=scores, test=test) + if 'far_value' in ctx.meta and ctx.meta['far_value'] is not None: + click.echo("Computing metrics with FAR=%f%s..." %\ + (ctx.meta['far_value'], log_str)) + ctx.meta['criter'] = 'far' # no criterio % n passed in evaluate + ctx.invoke(metrics, scores=scores, test=test) + + if ctx.meta['mindcf']: + click.echo("Computing minDCF%s..." % log_str) + ctx.meta['criter'] = 'mindcf' # no criterion passed in evaluate + ctx.invoke(metrics, scores=scores, test=test) + + if ctx.meta['cllr']: + click.echo("Computing Cllr and minCllr%s..." % log_str) + ctx.meta['criter'] = 'cllr' # no criterion passed in evaluate + ctx.invoke(metrics, scores=scores, test=test) + + if ctx.meta['rr']: + click.echo("Computing recognition rate%s..." % log_str) + ctx.meta['criter'] = 'rr' # no criterion passed in evaluate + ctx.invoke(metrics, scores=scores, test=test) + + # avoid closing pdf file before all figures are plotted + ctx.meta['closef'] = False + + if test: + click.echo("Starting evaluate with dev and test scores...") + else: + click.echo("Starting evaluate with dev scores only...") + + if ctx.meta['roc']: + click.echo("Generating ROC in %s..." % ctx.meta['output']) + ctx.forward(roc) # use class defaults plot settings + + if ctx.meta['det']: + click.echo("Generating DET in %s..." % ctx.meta['output']) + ctx.forward(det) # use class defaults plot settings + + if test and ctx.meta['epc']: + click.echo("Generating EPC in %s..." % ctx.meta['output']) + ctx.forward(epc) # use class defaults plot settings + + if ctx.meta['cmc']: + click.echo("Generating CMC in %s..." % ctx.meta['output']) + ctx.forward(cmc) # use class defaults plot settings + + if ctx.meta['dic']: + click.echo("Generating DIC in %s..." % ctx.meta['output']) + ctx.forward(dic) # use class defaults plot settings + + # the last one closes the file + if ctx.meta['hist']: + click.echo("Generating score histograms in %s..." % ctx.meta['output']) + ctx.meta['criter'] = 'hter' # no criterion passed in evaluate + ctx.forward(hist) + ctx.meta['closef'] = True + #just to make sure pdf is closed + if 'PdfPages' in ctx.meta: + ctx.meta['PdfPages'].close() + + click.echo("Evaluate successfully completed!") diff --git a/bob/bio/base/test/test_commands.py b/bob/bio/base/test/test_commands.py index 70d4c32f..5eb237bd 100644 --- a/bob/bio/base/test/test_commands.py +++ b/bob/bio/base/test/test_commands.py @@ -238,3 +238,67 @@ def test_dic(): if result.output: click.echo(result.output) assert result.exit_code == 0 + +def test_evaluate(): + dev1 = pkg_resources.resource_filename('bob.bio.base.test', + 'data/dev-4col.txt') + dev2 = pkg_resources.resource_filename('bob.bio.base.test', + 'data/dev-5col.txt') + + test1 = pkg_resources.resource_filename('bob.bio.base.test', + 'data/test-4col.txt') + test2 = pkg_resources.resource_filename('bob.bio.base.test', + 'data/test-5col.txt') + runner = CliRunner() + with runner.isolated_filesystem(): + result = runner.invoke(commands.evaluate, ['-l', 'tmp', '-f', 0.03, '-M', + '-x', '-m', dev1, dev2]) + assert result.exit_code == 0 + result = runner.invoke(commands.evaluate, ['-f', 0.02, '-M', + '-x', '-m', dev1, dev2]) + assert result.exit_code == 0 + + result = runner.invoke(commands.evaluate, ['-l', 'tmp', '-f', 0.04, '-M', + '-x', '-m', '-t', dev1, test1, + dev2, test2]) + assert result.exit_code == 0 + result = runner.invoke(commands.evaluate, ['-f', 0.01, '-M', '-t', + '-x', '-m', dev1, test1, dev2, + test2]) + assert result.exit_code == 0 + + result = runner.invoke(commands.evaluate, [dev1, dev2]) + assert result.exit_code == 0 + + result = runner.invoke(commands.evaluate, ['-R', '-D', '-H', '-E', + '-o', 'PLOTS.pdf', dev1, dev2]) + assert result.exit_code == 0 + + result = runner.invoke(commands.evaluate, ['-t', '-R', '-D', '-H', '-E', + '-o', 'PLOTS.pdf', + test1, dev1, test2, dev2]) + assert result.exit_code == 0 + + cmc = pkg_resources.resource_filename('bob.bio.base.test', + 'data/scores-cmc-4col.txt') + cmc2 = pkg_resources.resource_filename('bob.bio.base.test', + 'data/scores-cmc-5col.txt') + with runner.isolated_filesystem(): + result = runner.invoke(commands.evaluate, ['-r', cmc]) + assert result.exit_code == 0 + result = runner.invoke(commands.evaluate, ['-r', '-t', cmc, cmc2]) + assert result.exit_code == 0 + result = runner.invoke(commands.evaluate, ['-C', '-t', cmc, cmc2]) + assert result.exit_code == 0 + result = runner.invoke(commands.evaluate, ['-C', cmc, cmc2]) + assert result.exit_code == 0 + + cmc = pkg_resources.resource_filename('bob.bio.base.test', + 'data/scores-cmc-4col-open-set.txt') + cmc2 = pkg_resources.resource_filename('bob.bio.base.test', + 'data/scores-nonorm-openset-dev') + with runner.isolated_filesystem(): + result = runner.invoke(commands.evaluate, ['-O', cmc]) + assert result.exit_code == 0 + result = runner.invoke(commands.evaluate, ['-O', '-t', cmc, cmc2]) + assert result.exit_code == 0 diff --git a/setup.py b/setup.py index 3ce128af..c27e5ab2 100644 --- a/setup.py +++ b/setup.py @@ -138,14 +138,14 @@ setup( # bob bio scripts 'bob.bio.cli': [ 'annotate = bob.bio.base.script.annotate:annotate', - 'evaluate = bob.bio.base.script.evaluate:evaluate', 'metrics = bob.bio.base.script.commands:metrics', 'roc = bob.bio.base.script.commands:roc', 'det = bob.bio.base.script.commands:det', 'epc = bob.bio.base.script.commands:epc', - 'hist = bob.bio.base.script.commands:hist', + 'hist = bob.bio.base.script.commands:hist', 'cmc = bob.bio.base.script.commands:cmc', 'dic = bob.bio.base.script.commands:dic', + 'evaluate = bob.bio.base.script.commands:evaluate', ], # annotators -- GitLab