#!/usr/bin/env python3 import csv import math import random from argparse import ArgumentParser def norm(r, g, b): return math.sqrt(r * r + g * g + b * b) parser = ArgumentParser() parser.add_argument( "color", nargs="?", default=None, help="Target color, randomized if not provided" ) parser.add_argument( "-n", "--number", default=1, type=int, help="Number of Pokemon to find" ) parser.add_argument( "-c", "--closeness", default=2, type=int, help="Closeness coefficient" ) parser.add_argument("-d", "--database", default="database.csv", help="Database file") parser.add_argument("-x", "--exclude-x", action="store_true", help="Exclude X") parser.add_argument("-z", "--normalize", action="store_true", help="Normalize q and Y") parser.add_argument( "-j", "--convert-cam02", action="store_true", help="Convert input color to CIECAM02-UCS before calculation", ) parser.add_argument("-v", "--verbose", action="store_true", help="Print raw scores") args = parser.parse_args() if args.number <= 0: raise ValueError("Must request a number greater than 0") if args.color is not None: cleaned_color = args.color.strip("#") if len(cleaned_color) != 6: raise ValueError("Color must be a 6 digit hex") color = ( int(cleaned_color[0:2], base=16), int(cleaned_color[2:4], base=16), int(cleaned_color[4:6], base=16), ) else: color = tuple(int(random.random() * 255) for _ in range(3)) print(f"Generated random color: #{''.join(hex(c)[2:] for c in color)} / {color}") if args.convert_cam02: from colorspacious import cspace_convert color = list(cspace_convert(color, "sRGB255", "CAM02-UCS")) yfactor = args.closeness if args.normalize: yfactor /= norm(*color) results = [] with open(args.database) as infile: for name, x, *y in csv.reader(infile, delimiter=",", quotechar="'"): xval = 0 if args.exclude_x else float(x) yvec = [float(y_c) for y_c in y] yval = sum(y_c * c for y_c, c in zip(yvec, color)) if args.normalize: yval /= norm(*yvec) score = xval - yfactor * yval results.append((score, name)) if args.number > 1: for i, (score, name) in enumerate(sorted(results)[:args.number]): print(f"{i+1}:\t{name}") if args.verbose: print(f"\t\t\t{score=}") else: best_score, best_name = min(results) print(f"Best match: {best_name}") if args.verbose: print(f"{best_score=}")