common_options.py 18.8 KB
Newer Older
1 2 3 4
'''Stores click common options for plots'''

import logging
import click
5
from click.types import INT, FLOAT
6
import matplotlib.pyplot as plt
Amir MOHAMMADI's avatar
Amir MOHAMMADI committed
7
import tabulate
8
from matplotlib.backends.backend_pdf import PdfPages
9
from bob.extension.scripts.click_helper import (bool_option, list_float_option)
10

11
LOGGER = logging.getLogger(__name__)
12

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
13

14
def scores_argument(min_arg=1, force_eval=False, **kwargs):
15 16 17 18 19
    """Get the argument for scores, and add `dev-scores` and `eval-scores` in
    the context when `--evaluation` flag is on (default)

    Parameters
    ----------
20 21 22
    min_arg : int
        the minimum number of file needed to evaluate a system. For example,
        PAD functionalities needs licit abd spoof and therefore min_arg = 2
23 24 25

    Returns
    -------
26 27
     callable
      A decorator to be used for adding score arguments for click commands
28
    """
29 30
    def custom_scores_argument(func):
        def callback(ctx, param, value):
31 32 33
            min_a = min_arg or 1
            mutli = 1
            error = ''
34
            if ('evaluation' in ctx.meta and ctx.meta['evaluation']) or force_eval:
35 36 37 38 39
                mutli += 1
                error += '- %d evaluation file(s) \n' % min_a
            if 'train' in ctx.meta and ctx.meta['train']:
                mutli += 1
                error += '- %d training file(s) \n' % min_a
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
40
            # add more test here if other inputs are needed
41 42 43 44

            min_a *= mutli
            ctx.meta['min_arg'] = min_a
            if len(value) < 1 or len(value) % ctx.meta['min_arg'] != 0:
45
                raise click.BadParameter(
46 47 48 49
                    'The number of provided scores must be > 0 and a multiple of %d '
                    'because the following files are required:\n'
                    '- %d development file(s)\n' % (min_a, min_arg or 1) +
                    error, ctx=ctx
50
                )
51
            ctx.meta['scores'] = value
52
            return value
53 54 55 56
        return click.argument(
            'scores', type=click.Path(exists=True),
            callback=callback, **kwargs
        )(func)
57 58
    return custom_scores_argument

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
59

60 61 62 63 64 65 66 67
def no_legend_option(dflt=True, **kwargs):
    '''Get option flag to say if legend should be displayed or not'''
    return bool_option(
        'disp-legend', 'dl', 'If set, no legend will be printed.',
        dflt=dflt
    )


68 69 70 71 72 73
def eval_option(**kwargs):
    '''Get option flag to say if eval-scores are provided'''
    return bool_option(
        'evaluation', 'e', 'If set, evaluation scores must be provided',
        dflt=True
    )
74

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
75

76 77
def sep_dev_eval_option(dflt=True, **kwargs):
    '''Get option flag to say if dev and eval plots should be in different
78
    plots'''
79
    return bool_option(
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
80
        'split', 's', 'If set, evaluation and dev curve in different plots',
81
        dflt
82 83
    )

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
84

85 86 87 88 89
def linestyles_option(dflt=False, **kwargs):
    ''' Get option flag to turn on/off linestyles'''
    return bool_option('line-linestyles', 'S', 'If given, applies a different '
                       'linestyles to each line.', dflt, **kwargs)

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
90

91 92
def cmc_option(**kwargs):
    '''Get option flag to say if cmc scores'''
93 94
    return bool_option('cmc', 'C', 'If set, CMC score files are provided',
                       **kwargs)
95

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
96

97
def semilogx_option(dflt=False, **kwargs):
98
    '''Option to use semilog X-axis'''
99 100
    return bool_option('semilogx', 'G', 'If set, use semilog on X axis', dflt,
                       **kwargs)
101

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
102

103
def print_filenames_option(dflt=True, **kwargs):
104
    '''Option to tell if filenames should be in the title'''
105 106
    return bool_option('show-fn', 'P', 'If set, show filenames in title', dflt,
                       **kwargs)
107

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
108

109 110
def const_layout_option(dflt=True, **kwargs):
    '''Option to set matplotlib constrained_layout'''
111 112 113 114 115 116 117 118 119 120
    def custom_layout_option(func):
        def callback(ctx, param, value):
            ctx.meta['clayout'] = value
            plt.rcParams['figure.constrained_layout.use'] = value
            return value
        return click.option(
            '-Y', '--clayout/--no-clayout', default=dflt, show_default=True,
            help='(De)Activate constrained layout',
            callback=callback, **kwargs)(func)
    return custom_layout_option
121

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
122

123
def axes_val_option(dflt=None, **kwargs):
124
    ''' Option for setting min/max values on axes '''
125 126
    return list_float_option(
        name='axlim', short_name='L',
127 128
        desc='min/max axes values separated by commas (e.g. ``--axlim '
        ' 0.1,100,0.1,100``)',
129 130 131
        nitems=4, dflt=dflt, **kwargs
    )

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
132

133
def thresholds_option(**kwargs):
134
    ''' Option to give a list of thresholds '''
135 136
    return list_float_option(
        name='thres', short_name='T',
137 138
        desc='Given threshold for metrics computations, e.g. '
        '0.005,0.001,0.056',
139 140
        nitems=None, dflt=None, **kwargs
    )
141

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
142

Theophile GENTILHOMME's avatar
Theophile GENTILHOMME committed
143
def lines_at_option(dflt='1e-3', **kwargs):
144 145 146
    '''Get option to draw const far line'''
    return list_float_option(
        name='lines-at', short_name='la',
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
147
        desc='If given, draw vertical lines at the given axis positions. '
Theophile GENTILHOMME's avatar
Theophile GENTILHOMME committed
148
        'Your values must be separated with a comma (,) without space.',
Theophile GENTILHOMME's avatar
Theophile GENTILHOMME committed
149
        nitems=None, dflt=dflt, **kwargs
150 151
    )

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
152

153 154 155 156 157 158 159 160
def x_rotation_option(dflt=0, **kwargs):
    '''Get option for rotartion of the x axis lables'''
    def custom_x_rotation_option(func):
        def callback(ctx, param, value):
            value = abs(value)
            ctx.meta['x_rotation'] = value
            return value
        return click.option(
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
161 162
            '-r', '--x-rotation', type=click.INT, default=dflt,
            show_default=True, help='X axis labels ration',
163 164 165
            callback=callback, **kwargs)(func)
    return custom_x_rotation_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
166

167 168 169 170 171 172 173 174
def legend_ncols_option(dflt=10, **kwargs):
    '''Get option for number of columns for legends'''
    def custom_legend_ncols_option(func):
        def callback(ctx, param, value):
            value = abs(value)
            ctx.meta['legends_ncol'] = value
            return value
        return click.option(
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
175 176
            '-lc', '--legends-ncol', type=click.INT, default=dflt,
            show_default=True,
177 178 179 180
            help='The number of columns of the legend layout.',
            callback=callback, **kwargs)(func)
    return custom_legend_ncols_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
181

182 183 184 185 186 187 188 189 190 191 192
def subplot_option(dflt=111, **kwargs):
    '''Get option to set subplots'''
    def custom_subplot_option(func):
        def callback(ctx, param, value):
            value = abs(value)
            nrows = value // 10
            nrows, ncols = divmod(nrows, 10)
            ctx.meta['n_col'] = ncols
            ctx.meta['n_row'] = nrows
            return value
        return click.option(
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
193 194
            '-sp', '--subplot', type=click.INT, default=dflt,
            show_default=True, help='The order of subplots.',
195 196 197
            callback=callback, **kwargs)(func)
    return custom_subplot_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
198

199 200 201 202 203 204 205 206 207 208 209 210 211 212
def cost_option(**kwargs):
    '''Get option to get cost for FAR'''
    def custom_cost_option(func):
        def callback(ctx, param, value):
            if value < 0 or value > 1:
                raise click.BadParameter("Cost for FAR must be betwen 0 and 1")
            ctx.meta['cost'] = value
            return value
        return click.option(
            '-C', '--cost', type=float, default=0.99, show_default=True,
            help='Cost for FAR in minDCF',
            callback=callback, **kwargs)(func)
    return custom_cost_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
213

214 215 216 217 218 219
def points_curve_option(**kwargs):
    '''Get the number of points use to draw curves'''
    def custom_points_curve_option(func):
        def callback(ctx, param, value):
            if value < 2:
                raise click.BadParameter(
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
220 221
                    'Number of points to draw curves must be greater than 1',
                    ctx=ctx
222
                )
223
            ctx.meta['points'] = value
224 225 226 227 228 229 230
            return value
        return click.option(
            '-n', '--points', type=INT, default=100, show_default=True,
            help='The number of points use to draw curves in plots',
            callback=callback, **kwargs)(func)
    return custom_points_curve_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
231

232 233 234 235
def n_bins_option(**kwargs):
    '''Get the number of bins in the histograms'''
    def custom_n_bins_option(func):
        def callback(ctx, param, value):
236 237
            if value is None:
                value = 'auto'
238 239 240 241 242 243
            else:
                tmp = value.split(',')
                try:
                    value = [int(i) if i != 'auto' else i for i in tmp]
                except Exception:
                    raise click.BadParameter('Incorrect number of bins inputs')
244 245 246
            ctx.meta['n_bins'] = value
            return value
        return click.option(
247 248 249 250
            '-b', '--nbins', type=click.STRING, default='auto',
            help='The number of bins for the different histograms in the '
            ' figure, seperated by commas. For example, if three histograms '
            'are in the plots, input something like `100,auto,50`',
251 252 253
            callback=callback, **kwargs)(func)
    return custom_n_bins_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
254

255 256
def table_option(**kwargs):
    '''Get table option for tabulate package
Amir MOHAMMADI's avatar
Amir MOHAMMADI committed
257
    More informnations: https://pypi.org/project/tabulate/
258 259 260 261 262 263
    '''
    def custom_table_option(func):
        def callback(ctx, param, value):
            ctx.meta['tablefmt'] = value
            return value
        return click.option(
Amir MOHAMMADI's avatar
Amir MOHAMMADI committed
264 265
            '--tablefmt', type=click.Choice(tabulate.tabulate_formats),
            default='rst', show_default=True, help='Format of printed tables.',
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
266
            callback=callback, **kwargs)(func)
267 268
    return custom_table_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
269

270 271 272 273 274 275 276 277
def output_plot_file_option(default_out='plots.pdf', **kwargs):
    '''Get options for output file for plots'''
    def custom_output_plot_file_option(func):
        def callback(ctx, param, value):
            ''' Save ouput file  and associated pdf in context list,
            print the path of the file in the log'''
            ctx.meta['output'] = value
            ctx.meta['PdfPages'] = PdfPages(value)
278
            LOGGER.debug("Plots will be output in %s", value)
279 280 281 282 283 284 285 286
            return value
        return click.option(
            '-o', '--output',
            default=default_out, show_default=True,
            help='The file to save the plots in.',
            callback=callback, **kwargs)(func)
    return custom_output_plot_file_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
287

288
def output_log_metric_option(**kwargs):
289
    '''Get options for output file for metrics'''
290
    def custom_output_log_file_option(func):
291 292
        def callback(ctx, param, value):
            if value is not None:
293
                LOGGER.debug("Metrics will be output in %s", value)
294 295 296 297 298
            ctx.meta['log'] = value
            return value
        return click.option(
            '-l', '--log', default=None, type=click.STRING,
            help='If provided, computed numbers are written to '
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
299
            'this file instead of the standard output.',
300
            callback=callback, **kwargs)(func)
301
    return custom_output_log_file_option
302

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
303

304
def criterion_option(lcriteria=['eer', 'min-hter', 'far'], **kwargs):
305 306 307 308 309 310 311
    """Get option flag to tell which criteriom is used (default:eer)

    Parameters
    ----------
    lcriteria : :any:`list`
        List of possible criteria
    """
312
    def custom_criterion_option(func):
313 314
        list_accepted_crit = lcriteria if lcriteria is not None else \
        ['eer', 'min-hter', 'far']
315 316
        def callback(ctx, param, value):
            if value not in list_accepted_crit:
Theophile GENTILHOMME's avatar
Theophile GENTILHOMME committed
317
                raise click.BadParameter('Incorrect value for `--criterion`. '
318 319
                                         'Must be one of [`%s`]' %
                                         '`, `'.join(list_accepted_crit))
320
            ctx.meta['criterion'] = value
321 322
            return value
        return click.option(
323 324
            '-c', '--criterion', default='eer',
            help='Criterion to compute plots and '
325
            'metrics: %s)' % ', '.join(list_accepted_crit),
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
326
            callback=callback, is_eager=True, **kwargs)(func)
327 328
    return custom_criterion_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
329

330 331 332
def far_option(**kwargs):
    '''Get option to get far value'''
    def custom_far_option(func):
333
        def callback(ctx, param, value):
334
            if value is not None and (value > 1 or value < 0):
335 336
                raise click.BadParameter("FAR value should be between 0 and 1")
            ctx.meta['far_value'] = value
337 338
            return value
        return click.option(
339
            '-f', '--far-value', type=click.FLOAT, default=None,
340
            help='The FAR value for which to compute metrics',
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
341
            callback=callback, show_default=True, **kwargs)(func)
342
    return custom_far_option
343

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
344

345 346 347 348 349 350 351 352 353 354 355 356
def min_far_option(dflt=1e-4, **kwargs):
    '''Get option to get min far value'''
    def custom_min_far_option(func):
        def callback(ctx, param, value):
            if value is not None and (value > 1 or value < 0):
                raise click.BadParameter("FAR value should be between 0 and 1")
            ctx.meta['min_far_value'] = value
            return value
        return click.option(
            '-M', '--min-far-value', type=click.FLOAT, default=dflt,
            help='Select the minimum FAR value used in ROC and DET plots; '
            'should be a power of 10.',
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
357
            callback=callback, show_default=True, **kwargs)(func)
358 359
    return custom_min_far_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
360

361 362 363 364 365 366 367 368 369 370 371 372 373 374
def figsize_option(dflt='4,3', **kwargs):
    """Get option for matplotlib figsize

    Parameters
    ----------
    dflt : str
        matplotlib default figsize for the command. must be a a list of int
        separated by commas.

    Returns
    -------
    callable
        A decorator to be used for adding score arguments for click commands
    """
375 376 377 378
    def custom_figsize_option(func):
        def callback(ctx, param, value):
            ctx.meta['figsize'] = value if value is None else \
                    [float(x) for x in value.split(',')]
379 380
            if value is not None:
                plt.rcParams['figure.figsize'] = ctx.meta['figsize']
381 382
            return value
        return click.option(
383 384
            '--figsize', default=dflt, show_default=True,
            help='If given, will run '
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
385 386
            '``plt.rcParams[\'figure.figsize\']=figsize)``. '
            'Example: --fig-size 4,6',
387 388 389
            callback=callback, **kwargs)(func)
    return custom_figsize_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
390

391
def legend_loc_option(dflt='best', **kwargs):
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
392
    '''Get the legend location of the plot'''
393 394
    def custom_legend_loc_option(func):
        def callback(ctx, param, value):
395
            ctx.meta['legend_loc'] = value.replace('-', ' ') if value else value
396 397
            return value
        return click.option(
398
            '-lc', '--legend-loc', default=dflt, show_default=True,
399 400 401 402 403
            type=click.Choice(['best', 'upper-right', 'upper-left',
                               'lower-left', 'lower-right', 'right',
                               'center-left', 'center-right', 'lower-center',
                               'upper-center', 'center']),
            help='The legend location code',
404 405 406
            callback=callback, **kwargs)(func)
    return custom_legend_loc_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
407

408 409 410 411 412 413 414 415 416 417 418 419
def line_width_option(**kwargs):
    '''Get line width option for the plots'''
    def custom_line_width_option(func):
        def callback(ctx, param, value):
            ctx.meta['line_width'] = value
            return value
        return click.option(
            '--line-width',
            type=FLOAT, help='The line width of plots',
            callback=callback, **kwargs)(func)
    return custom_line_width_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
420

421 422 423 424 425 426 427 428 429 430 431 432
def marker_style_option(**kwargs):
    '''Get marker style otpion for the plots'''
    def custom_marker_style_option(func):
        def callback(ctx, param, value):
            ctx.meta['marker_style'] = value
            return value
        return click.option(
            '--marker-style',
            type=FLOAT, help='The marker style of the plots',
            callback=callback, **kwargs)(func)
    return custom_marker_style_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
433

434 435 436
def legends_option(**kwargs):
    '''Get the legends option for the different systems'''
    def custom_legends_option(func):
437
        def callback(ctx, param, value):
438 439
            if value is not None:
                value = value.split(',')
440
            ctx.meta['legends'] = value
441 442
            return value
        return click.option(
443
            '-lg', '--legends', type=click.STRING, default=None,
444
            help='The title for each system comma separated. '
445
            'Example: --legends ISV,CNN',
446
            callback=callback, **kwargs)(func)
447
    return custom_legends_option
448

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
449

450 451 452 453 454 455 456 457
def title_option(**kwargs):
    '''Get the title option for the different systems'''
    def custom_title_option(func):
        def callback(ctx, param, value):
            ctx.meta['title'] = value
            return value
        return click.option(
            '-t', '--title', type=click.STRING, default=None,
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
458 459
            help="The title of the plots. Provide just a space (-t ' ') to "
            "remove the titles from figures.",
460 461 462
            callback=callback, **kwargs)(func)
    return custom_title_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
463

464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
def titles_option(**kwargs):
    '''Get the titles option for the different plots'''
    def custom_title_option(func):
        def callback(ctx, param, value):
            if value is not None:
                value = value.split(',')
            ctx.meta['titles'] = value or []
            return value or []
        return click.option(
            '-ts', '--titles', type=click.STRING, default=None,
            help='The titles of the plots seperated by commas. '
            'For example, if the figure has two plots, \"MyTitleA,MyTitleB\" '
            'is a possible input'
            'Provide just a space (-t ' ') to '
            'remove the titles from figures.',
            callback=callback, **kwargs)(func)
    return custom_title_option


483 484 485 486 487 488 489 490
def x_label_option(dflt=None, **kwargs):
    '''Get the label option for X axis '''
    def custom_x_label_option(func):
        def callback(ctx, param, value):
            ctx.meta['x_label'] = value
            return value
        return click.option(
            '-xl', '--x-lable', type=click.STRING, default=dflt,
Amir MOHAMMADI's avatar
Amir MOHAMMADI committed
491
            show_default=True, help='Label for x-axis',
492 493 494
            callback=callback, **kwargs)(func)
    return custom_x_label_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
495

496 497 498 499 500 501 502 503 504 505 506 507
def y_label_option(dflt=None, **kwargs):
    '''Get the label option for Y axis '''
    def custom_y_label_option(func):
        def callback(ctx, param, value):
            ctx.meta['y_label'] = value
            return value
        return click.option(
            '-yl', '--y-lable', type=click.STRING, default=dflt,
            help='Label for y-axis',
            callback=callback, **kwargs)(func)
    return custom_y_label_option

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
508

509 510 511 512 513 514 515 516
def style_option(**kwargs):
    '''Get option for matplotlib style'''
    def custom_style_option(func):
        def callback(ctx, param, value):
            ctx.meta['style'] = value
            plt.style.use(value)
            return value
        return click.option(
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
517 518
            '--style', multiple=True,
            type=click.types.Choice(sorted(plt.style.available)),
519 520 521 522
            help='The matplotlib style to use for plotting. You can provide '
            'multiple styles by repeating this option',
            callback=callback, **kwargs)(func)
    return custom_style_option