1#!/usr/bin/env vpython
2# Copyright 2020 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import argparse
7import logging
8import sys
9
10import gold_inexact_matching.base_parameter_optimizer as base_optimizer
11import gold_inexact_matching.binary_search_parameter_optimizer\
12    as binary_optimizer
13import gold_inexact_matching.brute_force_parameter_optimizer as brute_optimizer
14import gold_inexact_matching.local_minima_parameter_optimizer\
15    as local_optimizer
16from gold_inexact_matching import optimizer_set
17
18# Script to find suitable values for Skia Gold inexact matching.
19#
20# Inexact matching in Skia Gold has three tunable parameters:
21#   1. The max number of differing pixels.
22#   2. The max delta for any single pixel.
23#   3. The threshold for a Sobel filter.
24#
25# Ideally, we use the following hierarchy of comparison approaches:
26#   1. Exact matching.
27#   2. Exact matching after a Sobel filter is applied.
28#   3. Fuzzy matching after a Sobel filter is applied.
29#
30# However, there may be cases where only using a Sobel filter requires masking a
31# very large amount of the image compared to Sobel + very conservative fuzzy
32# matching.
33#
34# Even if such cases are not hit, the process of determining good values for the
35# parameters is quite tedious since it requires downloading images from Gold and
36# manually running multiple calls to `goldctl match`.
37#
38# This script attempts to remedy both issues by handling all of the trial and
39# error and suggesting potential parameter values for the user to choose from.
40
41
42def CreateArgumentParser():
43  parser = argparse.ArgumentParser(
44      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
45  script_parser = parser.add_argument_group('Script Arguments')
46  script_parser.add_argument(
47      '-v',
48      '--verbose',
49      dest='verbose_count',
50      default=0,
51      action='count',
52      help='Verbose level (multiple times for more')
53
54  subparsers = parser.add_subparsers(help='Optimization algorithm')
55
56  binary_parser = subparsers.add_parser(
57      'binary_search',
58      formatter_class=argparse.ArgumentDefaultsHelpFormatter,
59      help='Perform a binary search to optimize a single parameter. The best '
60      'option if you only want to tune one parameter.')
61  binary_parser.set_defaults(
62      clazz=binary_optimizer.BinarySearchParameterOptimizer)
63  binary_optimizer.BinarySearchParameterOptimizer.AddArguments(binary_parser)
64
65  local_parser = subparsers.add_parser(
66      'local_minima',
67      formatter_class=argparse.ArgumentDefaultsHelpFormatter,
68      help='Perform a BFS to find local minima using weights for each '
69      'parameter. Slower than binary searching, but supports an arbitrary '
70      'number of parameters.')
71  local_parser.set_defaults(clazz=local_optimizer.LocalMinimaParameterOptimizer)
72  local_optimizer.LocalMinimaParameterOptimizer.AddArguments(local_parser)
73
74  brute_parser = subparsers.add_parser(
75      'brute_force',
76      formatter_class=argparse.ArgumentDefaultsHelpFormatter,
77      help='Brute force all possible combinations. VERY, VERY slow, but can '
78      'potentially find better values than local_minima.')
79  brute_parser.set_defaults(clazz=brute_optimizer.BruteForceParameterOptimizer)
80  brute_optimizer.BruteForceParameterOptimizer.AddArguments(brute_parser)
81
82  return parser
83
84
85def SetLoggingVerbosity(args):
86  logger = logging.getLogger()
87  if args.verbose_count == 0:
88    logger.setLevel(logging.WARNING)
89  elif args.verbose_count == 1:
90    logger.setLevel(logging.INFO)
91  else:
92    logger.setLevel(logging.DEBUG)
93
94
95def main():
96  parser = CreateArgumentParser()
97  args = parser.parse_args()
98  SetLoggingVerbosity(args)
99  optimizer = optimizer_set.OptimizerSet(args, args.clazz)
100  optimizer.RunOptimization()
101  return 0
102
103
104if __name__ == '__main__':
105  sys.exit(main())
106