common_options.py 17.7 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
7
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
8
from bob.extension.scripts.click_helper import (bool_option, list_float_option)
9

10
LOGGER = logging.getLogger(__name__)
11

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
12

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

    Parameters
    ----------
19
20
21
    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
22
23
24

    Returns
    -------
25
26
     callable
      A decorator to be used for adding score arguments for click commands
27
    """
28
29
    def custom_scores_argument(func):
        def callback(ctx, param, value):
30
31
32
            min_a = min_arg or 1
            mutli = 1
            error = ''
33
            if ('evaluation' in ctx.meta and ctx.meta['evaluation']) or force_eval:
34
35
36
37
38
                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
39
            # add more test here if other inputs are needed
40
41
42
43

            min_a *= mutli
            ctx.meta['min_arg'] = min_a
            if len(value) < 1 or len(value) % ctx.meta['min_arg'] != 0:
44
                raise click.BadParameter(
45
46
47
48
                    '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
49
                )
50
            ctx.meta['scores'] = value
51
            return value
52
53
54
55
        return click.argument(
            'scores', type=click.Path(exists=True),
            callback=callback, **kwargs
        )(func)
56
57
    return custom_scores_argument

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
58

59
60
61
62
63
64
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
    )
65

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
66

67
68
def sep_dev_eval_option(dflt=True, **kwargs):
    '''Get option flag to say if dev and eval plots should be in different
69
    plots'''
70
    return bool_option(
Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
71
        'split', 's', 'If set, evaluation and dev curve in different plots',
72
        dflt
73
74
    )

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
75

76
77
78
79
80
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
81

82
83
def cmc_option(**kwargs):
    '''Get option flag to say if cmc scores'''
84
85
    return bool_option('cmc', 'C', 'If set, CMC score files are provided',
                       **kwargs)
86

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
87

88
def semilogx_option(dflt=False, **kwargs):
89
    '''Option to use semilog X-axis'''
90
91
    return bool_option('semilogx', 'G', 'If set, use semilog on X axis', dflt,
                       **kwargs)
92

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
93

94
def print_filenames_option(dflt=True, **kwargs):
95
    '''Option to tell if filenames should be in the title'''
96
97
    return bool_option('show-fn', 'P', 'If set, show filenames in title', dflt,
                       **kwargs)
98

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
99

100
101
def const_layout_option(dflt=True, **kwargs):
    '''Option to set matplotlib constrained_layout'''
102
103
104
105
106
107
108
109
110
111
    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
112

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
113

114
def axes_val_option(dflt=None, **kwargs):
115
    ''' Option for setting min/max values on axes '''
116
117
    return list_float_option(
        name='axlim', short_name='L',
118
119
        desc='min/max axes values separated by commas (e.g. ``--axlim '
        ' 0.1,100,0.1,100``)',
120
121
122
        nitems=4, dflt=dflt, **kwargs
    )

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
123

124
def thresholds_option(**kwargs):
125
    ''' Option to give a list of thresholds '''
126
127
    return list_float_option(
        name='thres', short_name='T',
128
129
        desc='Given threshold for metrics computations, e.g. '
        '0.005,0.001,0.056',
130
131
        nitems=None, dflt=None, **kwargs
    )
132

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
133

134
135
136
137
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
138
139
        desc='If given, draw vertical lines at the given axis positions. '
        'You can provide multiple values separated with a comma (,).',
140
        nitems=None, dflt='1e-3', **kwargs
141
142
    )

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
143

144
145
146
147
148
149
150
151
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
152
153
            '-r', '--x-rotation', type=click.INT, default=dflt,
            show_default=True, help='X axis labels ration',
154
155
156
            callback=callback, **kwargs)(func)
    return custom_x_rotation_option

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
157

158
159
160
161
162
163
164
165
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
166
167
            '-lc', '--legends-ncol', type=click.INT, default=dflt,
            show_default=True,
168
169
170
171
            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
172

173
174
175
176
177
178
179
180
181
182
183
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
184
185
            '-sp', '--subplot', type=click.INT, default=dflt,
            show_default=True, help='The order of subplots.',
186
187
188
            callback=callback, **kwargs)(func)
    return custom_subplot_option

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
189

190
191
192
193
194
195
196
197
198
199
200
201
202
203
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
204

205
206
207
208
209
210
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
211
212
                    'Number of points to draw curves must be greater than 1',
                    ctx=ctx
213
                )
214
            ctx.meta['points'] = value
215
216
217
218
219
220
221
            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
222

223
224
225
226
def n_bins_option(**kwargs):
    '''Get the number of bins in the histograms'''
    def custom_n_bins_option(func):
        def callback(ctx, param, value):
227
228
            if value is None:
                value = 'auto'
229
230
231
232
233
234
            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')
235
236
237
            ctx.meta['n_bins'] = value
            return value
        return click.option(
238
239
240
241
            '-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`',
242
243
244
            callback=callback, **kwargs)(func)
    return custom_n_bins_option

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
245

246
247
248
249
250
251
252
253
254
def table_option(**kwargs):
    '''Get table option for tabulate package
    More informnations: https://pypi.python.org/pypi/tabulate
    '''
    def custom_table_option(func):
        def callback(ctx, param, value):
            ctx.meta['tablefmt'] = value
            return value
        return click.option(
255
            '--tablefmt', type=click.STRING, default='rst',
256
            show_default=True, help='Format for table display: `plain`, '
257
            '`simple`, `grid`, `fancy_grid`, `pipe`, `orgtbl`, '
258
259
            '`jira`, `presto`, `psql`, `rst`, `mediawiki`, `moinmoin`, '
            '`youtrack`, `html`, `latex`, '
260
            '`latex_raw`, `latex_booktabs`, `textile`',
Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
261
            callback=callback, **kwargs)(func)
262
263
    return custom_table_option

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
264

265
266
267
268
269
270
271
272
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)
273
            LOGGER.debug("Plots will be output in %s", value)
274
275
276
277
278
279
280
281
            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
282

283
def output_log_metric_option(**kwargs):
284
    '''Get options for output file for metrics'''
285
    def custom_output_log_file_option(func):
286
287
        def callback(ctx, param, value):
            if value is not None:
288
                LOGGER.debug("Metrics will be output in %s", value)
289
290
291
292
293
            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
294
            'this file instead of the standard output.',
295
            callback=callback, **kwargs)(func)
296
    return custom_output_log_file_option
297

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
298

299
def criterion_option(lcriteria=['eer', 'min-hter', 'far'], **kwargs):
300
301
302
303
304
305
306
    """Get option flag to tell which criteriom is used (default:eer)

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

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
324

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

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
339

340
341
342
343
344
345
346
347
348
349
350
351
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
352
            callback=callback, show_default=True, **kwargs)(func)
353
354
    return custom_min_far_option

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
355

356
357
358
359
360
361
362
363
364
365
366
367
368
369
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
    """
370
371
372
373
    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(',')]
374
375
            if value is not None:
                plt.rcParams['figure.figsize'] = ctx.meta['figsize']
376
377
            return value
        return click.option(
378
            '--figsize', default=dflt, help='If given, will run '
Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
379
380
            '``plt.rcParams[\'figure.figsize\']=figsize)``. '
            'Example: --fig-size 4,6',
381
382
383
            callback=callback, **kwargs)(func)
    return custom_figsize_option

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
384

385
def legend_loc_option(**kwargs):
Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
386
    '''Get the legend location of the plot'''
387
388
389
390
391
392
    def custom_legend_loc_option(func):
        def callback(ctx, param, value):
            ctx.meta['legend_loc'] = value
            return value
        return click.option(
            '--legend-location', default=0, show_default=True,
Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
393
            type=INT, help='The legend location code',
394
395
396
            callback=callback, **kwargs)(func)
    return custom_legend_loc_option

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
397

398
399
400
401
402
403
404
405
406
407
408
409
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
410

411
412
413
414
415
416
417
418
419
420
421
422
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
423

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

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
439

440
441
442
443
444
445
446
447
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
448
449
            help="The title of the plots. Provide just a space (-t ' ') to "
            "remove the titles from figures.",
450
451
452
            callback=callback, **kwargs)(func)
    return custom_title_option

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
453

454
455
456
457
458
459
460
461
462
463
464
465
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,
            help='Label for x-axis',
            callback=callback, **kwargs)(func)
    return custom_x_label_option

Amir MOHAMMADI's avatar
lint    
Amir MOHAMMADI committed
466

467
468
469
470
471
472
473
474
475
476
477
478
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
479

480
481
482
483
484
485
486
487
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
488
489
            '--style', multiple=True,
            type=click.types.Choice(sorted(plt.style.available)),
490
491
492
493
            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