common_options.py 17.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

143
144
145
146
def lines_at_option(**kwargs):
    '''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
148
        desc='If given, draw vertical lines at the given axis positions. '
        'You can provide multiple values separated with a comma (,).',
149
        nitems=None, dflt='1e-3', **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
313
    def custom_criterion_option(func):
        def callback(ctx, param, value):
314
            list_accepted_crit = lcriteria if lcriteria is not None else \
Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
315
                ['eer', 'min-hter', 'far']
316
            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: `eer`, `min-hter` or `far`',
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=0, **kwargs):
Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
392
    '''Get the legend location of the plot'''
393
394
395
396
397
    def custom_legend_loc_option(func):
        def callback(ctx, param, value):
            ctx.meta['legend_loc'] = value
            return value
        return click.option(
398
            '-lc', '--legend-loc', default=dflt, show_default=True,
Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
399
            type=INT, help='The legend location code',
400
401
402
            callback=callback, **kwargs)(func)
    return custom_legend_loc_option

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
403

404
405
406
407
408
409
410
411
412
413
414
415
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
416

417
418
419
420
421
422
423
424
425
426
427
428
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
429

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

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
445

446
447
448
449
450
451
452
453
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
454
455
            help="The title of the plots. Provide just a space (-t ' ') to "
            "remove the titles from figures.",
456
457
458
            callback=callback, **kwargs)(func)
    return custom_title_option

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
459

460
461
462
463
464
465
466
467
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
468
            show_default=True, help='Label for x-axis',
469
470
471
            callback=callback, **kwargs)(func)
    return custom_x_label_option

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
472

473
474
475
476
477
478
479
480
481
482
483
484
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
485

486
487
488
489
490
491
492
493
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
494
495
            '--style', multiple=True,
            type=click.types.Choice(sorted(plt.style.available)),
496
497
498
499
            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