nearest.py 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. #!/usr/bin/env python3
  2. import csv
  3. import math
  4. import random
  5. from argparse import ArgumentParser
  6. def norm(r, g, b):
  7. return math.sqrt(r * r + g * g + b * b)
  8. parser = ArgumentParser()
  9. parser.add_argument(
  10. "color", nargs="?", default=None, help="Target color, randomized if not provided"
  11. )
  12. parser.add_argument(
  13. "-n", "--number", default=1, type=int, help="Number of Pokemon to find"
  14. )
  15. parser.add_argument(
  16. "-c", "--closeness", default=2, type=int, help="Closeness coefficient"
  17. )
  18. parser.add_argument("-d", "--database", default="database.csv", help="Database file")
  19. parser.add_argument("-x", "--exclude-x", action="store_true", help="Exclude X")
  20. parser.add_argument("-z", "--normalize", action="store_true", help="Normalize q and Y")
  21. parser.add_argument(
  22. "-j",
  23. "--convert-cam02",
  24. action="store_true",
  25. help="Convert input color to CIECAM02-UCS before calculation",
  26. )
  27. parser.add_argument("-v", "--verbose", action="store_true", help="Print raw scores")
  28. args = parser.parse_args()
  29. if args.number <= 0:
  30. raise ValueError("Must request a number greater than 0")
  31. if args.color is not None:
  32. cleaned_color = args.color.strip("#")
  33. if len(cleaned_color) != 6:
  34. raise ValueError("Color must be a 6 digit hex")
  35. color = (
  36. int(cleaned_color[0:2], base=16),
  37. int(cleaned_color[2:4], base=16),
  38. int(cleaned_color[4:6], base=16),
  39. )
  40. else:
  41. color = tuple(int(random.random() * 255) for _ in range(3))
  42. print(f"Generated random color: #{''.join(hex(c)[2:] for c in color)} / {color}")
  43. if args.convert_cam02:
  44. from colorspacious import cspace_convert
  45. color = list(cspace_convert(color, "sRGB255", "CAM02-UCS"))
  46. yfactor = args.closeness
  47. if args.normalize:
  48. yfactor /= norm(*color)
  49. results = []
  50. with open(args.database) as infile:
  51. for name, x, *y in csv.reader(infile, delimiter=",", quotechar="'"):
  52. xval = 0 if args.exclude_x else float(x)
  53. yvec = [float(y_c) for y_c in y]
  54. yval = sum(y_c * c for y_c, c in zip(yvec, color))
  55. if args.normalize:
  56. yval /= norm(*yvec)
  57. score = xval - yfactor * yval
  58. results.append((score, name))
  59. if args.number > 1:
  60. for i, (score, name) in enumerate(sorted(results)[:args.number]):
  61. print(f"{i+1}:\t{name}")
  62. if args.verbose:
  63. print(f"\t\t\t{score=}")
  64. else:
  65. best_score, best_name = min(results)
  66. print(f"Best match: {best_name}")
  67. if args.verbose:
  68. print(f"{best_score=}")