From 86bfbd101b89651297868b72b1ded393ce2db1ab Mon Sep 17 00:00:00 2001 From: Rokuz Date: Mon, 20 Nov 2017 14:45:55 +0300 Subject: [PATCH] Fixed color comparison --- data/transit_colors.txt | 15 +-- tools/python/transit/transit_color_palette.py | 104 +++++++++++++++++- .../python/transit/transit_graph_generator.py | 52 ++++++++- 3 files changed, 151 insertions(+), 20 deletions(-) diff --git a/data/transit_colors.txt b/data/transit_colors.txt index 676d18a108..7f19557a9a 100644 --- a/data/transit_colors.txt +++ b/data/transit_colors.txt @@ -6,7 +6,7 @@ "text": "231F20" }, "gray": { - "clear": "8B8B8C", + "clear": "A4A4A6", "night": "464647", "text": "231F20" }, @@ -121,17 +121,17 @@ "text": "01579B" }, "blue_light": { - "clear": "90CAF9", + "clear": "B2D8F8", "night": "466178", "text": "01579B" }, "navy_dark": { - "clear": "1A357D", + "clear": "193380", "night": "0F1B42", "text": "1A357D" }, "navy": { - "clear": "0068AD", + "clear": "243BB3", "night": "003557", "text": "1A357D" }, @@ -140,13 +140,8 @@ "night": "3D4866", "text": "1A357D" }, - "indigo": { - "clear": "4824B5", - "night": "25135B", - "text": "4A148C" - }, "purple_dark": { - "clear": "4A148C", + "clear": "52138B", "night": "360E66", "text": "4A148C" }, diff --git a/tools/python/transit/transit_color_palette.py b/tools/python/transit/transit_color_palette.py index 054dfd92ae..df44ce69bf 100644 --- a/tools/python/transit/transit_color_palette.py +++ b/tools/python/transit/transit_color_palette.py @@ -1,3 +1,5 @@ +import math + def to_rgb(color_str): if len(color_str) != 6: return (0, 0, 0) @@ -7,6 +9,90 @@ def to_rgb(color_str): return (r, g, b) +def blend_colors(rgb_array1, rgb_array2, k): + return (rgb_array1[0] * (1.0 - k) + rgb_array2[0] * k, + rgb_array1[1] * (1.0 - k) + rgb_array2[1] * k, + rgb_array1[2] * (1.0 - k) + rgb_array2[2] * k) + + +def rgb_pivot(n): + result = n / 12.92 + if n > 0.04045: + result = ((n + 0.055) / 1.055) ** 2.4 + return result * 100.0; + + +def to_xyz(rgb_array): + r = rgb_pivot(rgb_array[0] / 255.0); + g = rgb_pivot(rgb_array[1] / 255.0); + b = rgb_pivot(rgb_array[2] / 255.0); + return (r * 0.4124 + g * 0.3576 + b * 0.1805, + r * 0.2126 + g * 0.7152 + b * 0.0722, + r * 0.0193 + g * 0.1192 + b * 0.9505) + + +#https://en.wikipedia.org/wiki/Lab_color_space#CIELAB +def lab_pivot(n): + if n > 0.008856: + return n ** (1.0/3.0) + return (903.3 * n + 16.0) / 116.0 + + +def to_lab(rgb_array): + xyz = to_xyz(rgb_array) + x = lab_pivot(xyz[0] / 95.047) + y = lab_pivot(xyz[1] / 100.0) + z = lab_pivot(xyz[2] / 108.883) + l = 116.0 * y - 16.0 + if l < 0.0: + l = 0.0 + a = 500.0 * (x - y) + b = 200.0 * (y - z) + return (l, a, b) + + +def lum_distance(ref_color, src_color): + return 30 * (ref_color[0] - src_color[0]) ** 2 +\ + 59 * (ref_color[1] - src_color[1]) ** 2 +\ + 11 * (ref_color[2] - src_color[2]) ** 2 + + +def is_bluish(rgb_array): + d1 = lum_distance((255, 0, 0), rgb_array) + d2 = lum_distance((0, 0, 255), rgb_array) + return d2 < d1 + + +#http://en.wikipedia.org/wiki/Color_difference#CIE94 +def cie94(ref_color, src_color): + lab_ref = to_lab(ref_color) + lab_src = to_lab(src_color) + deltaL = lab_ref[0] - lab_src[0] + deltaA = lab_ref[1] - lab_src[1] + deltaB = lab_ref[2] - lab_src[2] + c1 = math.sqrt(lab_ref[0] * lab_ref[0] + lab_ref[1] * lab_ref[1]) + c2 = math.sqrt(lab_src[0] * lab_src[0] + lab_src[1] * lab_src[1]) + deltaC = c1 - c2 + deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC + if deltaH < 0.0: + deltaH = 0.0 + else: + deltaH = math.sqrt(deltaH) + # cold tones if a color is more bluish. + Kl = 1.0 + K1 = 0.045 + K2 = 0.015 + sc = 1.0 + K1 * c1 + sh = 1.0 + K2 * c1 + deltaLKlsl = deltaL / Kl + deltaCkcsc = deltaC / sc + deltaHkhsh = deltaH / sh + i = deltaLKlsl * deltaLKlsl + deltaCkcsc * deltaCkcsc + deltaHkhsh * deltaHkhsh + if i < 0: + return 0.0 + return math.sqrt(i) + + class Palette: def __init__(self, colors): self.colors = {} @@ -16,16 +102,26 @@ class Palette: def get_default_color(self): return 'default' - def get_nearest_color(self, color_str): + def get_nearest_color(self, color_str, casing_color_str, excluded_names): """Returns the nearest color from the palette.""" nearest_color_name = self.get_default_color() color = to_rgb(color_str) + if (casing_color_str is not None and len(casing_color_str) != 0): + color = blend_colors(color, to_rgb(casing_color_str), 0.5) min_diff = None + + bluish = is_bluish(color) for name, palette_color in self.colors.iteritems(): - diff = 30 * (palette_color[0] - color[0]) ** 2 +\ - 59 * (palette_color[1] - color[1]) ** 2 +\ - 11 * (palette_color[2] - color[2]) ** 2 + if name in excluded_names: + continue + if bluish: + diff = lum_distance(palette_color, color) + else: + diff = cie94(palette_color, color) if min_diff is None or diff < min_diff: min_diff = diff nearest_color_name = name + # Left here for debug purposes. + #print("Result: " + color_str + "," + str(casing_color_str) + + # " - " + nearest_color_name + ": bluish = " + str(bluish)) return nearest_color_name diff --git a/tools/python/transit/transit_graph_generator.py b/tools/python/transit/transit_graph_generator.py index cc9e407474..acf6091416 100755 --- a/tools/python/transit/transit_graph_generator.py +++ b/tools/python/transit/transit_graph_generator.py @@ -6,6 +6,7 @@ import copy import json import math import matplotlib.pyplot as plt +import matplotlib.patches as patches import numpy as np import os.path import sys, io @@ -70,6 +71,7 @@ class TransitGraphBuilder: self.segments = {} self.shapes = [] self.transit_graph = None + self.matched_colors = {} def __get_average_stops_point(self, stop_ids): """Returns an average position of the stops.""" @@ -176,10 +178,7 @@ class TransitGraphBuilder: 'interval': line_item['interval'], 'stop_ids': [] } - if 'colour' in route_item: - line['color'] = self.palette.get_nearest_color(route_item['colour']) - else: - line['color'] = self.palette.get_default_color() + line['color'] = self.__match_color(route_item.get('colour', ''), route_item.get('casing', '')) # TODO: Add processing of line_item['shape'] when this data will be available. # TODO: Add processing of line_item['trip_ids'] when this data will be available. @@ -203,6 +202,17 @@ class TransitGraphBuilder: self.lines.append(line) line_index += 1 + def __match_color(self, color_str, casing_str): + if len(color_str) == 0: + return self.palette.get_default_color() + matched_colors_key = color_str + "/" + casing_str + if matched_colors_key in self.matched_colors: + return self.matched_colors[matched_colors_key] + c = self.palette.get_nearest_color(color_str, casing_str, self.matched_colors.values()) + if c != self.palette.get_default_color(): + self.matched_colors[matched_colors_key] = c + return c + def __generate_transfer_nodes(self): """Merges stops into transfer nodes.""" for edge in self.edges: @@ -332,6 +342,27 @@ class TransitGraphBuilder: plt.scatter([point['x']], [point['y']], size, color) plt.show() + def show_color_maching_table(self, title, colors_ref_table): + fig = plt.figure() + ax = fig.add_subplot(111, aspect='equal') + plt.title(title) + sz = 1.0 / (2.0 * len(self.matched_colors)) + delta_y = sz * 0.5 + for c in self.matched_colors: + tokens = c.split('/') + if len(tokens[1]) == 0: + tokens[1] = tokens[0] + ax.add_patch(patches.Rectangle((sz, delta_y), sz, sz, facecolor="#" + tokens[0], edgecolor="#" + tokens[1])) + rect_title = tokens[0] + if tokens[0] != tokens[1]: + rect_title += "/" + tokens[1] + ax.text(2.5 * sz, delta_y, rect_title + " -> ") + ref_color = colors_ref_table[self.matched_colors[c]] + ax.add_patch(patches.Rectangle((0.3 + sz, delta_y), sz, sz, facecolor="#" + ref_color)) + ax.text(0.3 + 2.5 * sz, delta_y, ref_color + " (" + self.matched_colors[c] + ")") + delta_y += sz * 2.0 + plt.show() + if __name__ == '__main__': parser = argparse.ArgumentParser() @@ -342,6 +373,9 @@ if __name__ == '__main__': help='transit colors file COLORS_FILE_PATH', metavar='COLORS_FILE_PATH') parser.add_argument('-p', '--preview', action="store_true", default=False, help="show preview of the transit scheme") + parser.add_argument('-m', '--matched_colors', action="store_true", default=False, + help="show the matched colors table") + parser.add_argument('-a', '--alpha', type=float, default=0.5, help='the curves generator parameter value ALPHA', metavar='ALPHA') @@ -360,9 +394,9 @@ if __name__ == '__main__': result = transit.build() output_file = args.output_file + head, tail = os.path.split(os.path.abspath(args.input_file)) + name, extension = os.path.splitext(tail) if output_file is None: - head, tail = os.path.split(os.path.abspath(args.input_file)) - name, extension = os.path.splitext(tail) output_file = os.path.join(head, name + '.transit' + extension) with io.open(output_file, 'w', encoding='utf8') as json_file: result_data = json.dumps(result, ensure_ascii=False, indent=4, sort_keys=True) @@ -371,3 +405,9 @@ if __name__ == '__main__': if args.preview: transit.show_preview() + + if args.matched_colors: + colors_ref_table = {} + for color_name, color_info in colors['colors'].iteritems(): + colors_ref_table[color_name] = color_info['clear'] + transit.show_color_maching_table(name, colors_ref_table)