* FAQ    * Search  * Register * Login 
Active topics
Unanswered topics

All times are UTC-06:00

Post new topic  Reply to topic  [ 5 posts ] 
Author Message
 Post subject: Color wheel corrected for human discernment
PostPosted: Thu Dec 04, 2014 6:21 pm 
DBB Master
DBB Master
User avatar

Joined: Sun Sep 05, 1999 2:01 am
Posts: 6356
Location: ☃☃☃
I'm making for myself some hiking maps in the form of *.kml files that I can import into Google Earth. I wanted each hiking trail to have a different color, and so I assigned each trail a random color from the rainbow. Specifically, if there are n trails, then for each trail, I randomly choose a color (without replacement) from the sequence of colors printed by the following python function:

import colorsys

def rainbow(n):
    for i in xrange(n):
        rgb = colorsys.hsv_to_rgb(float(i) / n, 1.0, 1.0)
        rgb = tuple(round(x * 255) for x in rgb)
        print '#%.2x%.2x%.2x' % rgb

In this sequence, each color has approximately equidistant hue from its neighbors. In the maps I'm creating, most trails have many intersections and many other nearby trails in general. One thing I noticed is if two nearby trails colored in the purplish-reddish part of the spectrum happened to intersect each other, it was difficult to tell the trails apart, whereas this problem didn't exist in other parts of the spectrum, such as the yellowish-greenish part. I suspect that human vision is better at discerning between hues in some parts of the spectrum versus others. So how would I write a function similar to the above that, instead of generating colors with equidistant hues, generates colors with hues with distance such that each hue is equally discernible from its neighbors? For instance, in such a sequence, I'd imagine there would be fewer reddish-purplish colors, since they need to be of greater hue distance to tell apart, whereas there would be more yellowish-greenish colors, since they don't need as much hue distance to tell apart.

Another solution to the map problem in general is to try assign colors to trails not entirely randomly but such that trails that intersect each other or are otherwise nearby each other don't have similar colors, but I think that even if this solution were employed, coloring trails with the most easily discernible hues would still be valuable, for instance, for creating a map legend. Plus, this seems like an interesting problem that must have a known solution. But I haven't been able to come across anything so far. Any thoughts?

 Post subject: Re: Color wheel corrected for human discernment
PostPosted: Fri Dec 05, 2014 10:32 am 
DBB Material Defender
DBB Material Defender
User avatar

Joined: Tue Nov 23, 2004 3:31 pm
Posts: 4900
Location: Denver, Colorado, USA
A few thoughts, in no particular order:

  • Have you tried this on various monitors and in various lighting? I'm curious about whether the display is a factor in the difference in discernment in the different areas of the spectrum.

  • I currently work in aeronautical charting applications, and I can tell you that maximizing color-distance is pretty critical. That said, I don't know of any well-defined methods for achieving it. [e.g. Our interface design group at my workplace tweaks the coloring schemes for varying light conditions (day vs. night) from user experience; I don't know of any structured system they're using.]

  • You mentioned finding some kind of non-"linear" color-generation that weights less heavily on the red/purple area of the spectrum, but I honestly think your other solution (finding a mechanism to prevent near-colored routes in close proximity) would be more promising. Of course, that's not a trivial problem by any means...

 Post subject: Re: Color wheel corrected for human discernment
PostPosted: Thu Dec 11, 2014 3:10 pm 
DBB Master
DBB Master
User avatar

Joined: Sun Sep 05, 1999 2:01 am
Posts: 6356
Location: ☃☃☃
I found that there are colorspaces such as CIELAB and CIELUV that are perceptually uniform, that is, the human perceptual difference between colors in these spaces is the euclidean distance between the colors. So one way to solve this problem would be to choose maximally equidistant points in these spaces, but I don't think this is easy, as these colorspaces don't have simple shapes.

What I ended up returning to was the original problem of generating a perceptually uniform rainbow of n colors. First, I needed a perceptual distance metric. One such metric could be to just convert colors to the CIELAB colorspace and calculate their euclidean distance, but I ended up using the simpler metric proposed here:


Using this distance metric, for a given n, I perform binary search over perceptual distances to find the perceptual distance that evenly divided the rainbow into n divisions. To find each successive hue with perceptual distance d from the previous, I use binary search over hues.

Here are the results...

Rainbow with linear hues:
rainbow.png [ 466 Bytes | Viewed 958 times ]

Rainbow with perceptually uniform hues:
perceptual.png [ 520 Bytes | Viewed 958 times ]

This approach seems to have shrunk the purply-reddish part of the spectrum, like I was hoping it would. It also seems to have shrunk the green and the blue parts of the spectrum too, which I hadn't noticed in my maps as being a problem, but looking at the original rainbow, it seems like this should be helpful too. Maybe a different distance metric would have even better results?

Here's the code:

#!/usr/bin/env python

from math import sqrt
import sys

BLACK = 0.0, 0.0, 0.0
WHITE = 1.0, 1.0, 1.0

def distance(rgb1, rgb2):
    r1, g1, b1 = rgb1
    r2, g2, b2 = rgb2
    rmean = (r1 + r2) / 2.0
    rdiff = (r1 - r2) * 256
    gdiff = (g1 - g2) * 256
    bdiff = (b1 - b2) * 256
    rweight = 2 + rmean
    gweight = 4.0
    bweight = 2 + (1.0 - rmean)
    return sqrt(rweight * rdiff * rdiff +
                gweight * gdiff * gdiff +
                bweight * bdiff * bdiff)


def rgb_to_hex(rgb):
    return '#%.2x%.2x%.2x' % tuple(round(x * 255) for x in rgb)

def hue_to_rgb(h):
    i = int(h * 6.0)
    f = h * 6.0 - i
    g = 1.0 - f
    i %= 6
    if i == 0:
        return 1.0, f, 0.0
    elif i == 1:
        return g, 1.0, 0.0
    elif i == 2:
        return 0.0, 1.0, f
    elif i == 3:
        return 0.0, g, 1.0
    elif i == 4:
        return f, 0.0, 1.0
    else: # i == 5
        return 1.0, 0.0, g

def next_hue(h, desired_distance):
    h = max(0.0, min(1.0, h))
    rgb1 = hue_to_rgb(h)
    # Perform binary search over hues to find hue with desired perceptual
    # distance from h...
    lo = h
    hi = min(1.0, h + 0.5)
    while hi - lo >= 1e-12:
        mid = (lo + hi) / 2
        rgb2 = hue_to_rgb(mid)
        dist = distance(rgb1, rgb2)
        if dist <= desired_distance:
            lo = mid
        if dist >= desired_distance:
            hi = mid
    return hi

def rainbow(n):
    return [hue_to_rgb(float(i) / n) for i in xrange(n)]

def perceptual_rainbow(n):
    n = max(0, n)
    # Perform binary search over perceptual distance to find perceptual
    # distance that evenly divides all hues into n divisions...
    hues = [0.0] * (n + 1)
    lo = 0.0
    hi = MAX_DIST
    while hi - lo >= 1e-12:
        mid = (lo + hi) / 2
        for i in xrange(1, n + 1):
            h = next_hue(hues[i - 1], mid)
            hues[i] = h
        if hues[n] == 1.0:
            hi = mid
            lo = mid
    return [hue_to_rgb(hues[i]) for i in xrange(n)]

def usage():
    sys.stderr.write('Usage: %s N\n' % sys.argv[0])

def main():
    if len(sys.argv) != 2:
        n = int(sys.argv[1])
    except ValueError:
        for rgb in perceptual_rainbow(n):
            print rgb_to_hex(rgb)

if __name__ == '__main__':

Example usage:

$ python rainbow.py 10

 Post subject: Re: Color wheel corrected for human discernment
PostPosted: Fri Dec 12, 2014 7:19 am 
DBB Benefactor
DBB Benefactor
User avatar

Joined: Thu Sep 02, 1999 2:01 am
Posts: 4428
Another idea/suggestion:

Why not try to take advantage of the other dimensions in your color space? You would want to stay away from 0 saturation and 0 value, but if you treat spacing in a 3-dimensional sense (human-corrected or no), you're crippling yourself by using only a single ring.

Arch Linux x86-64, Openbox
"We'll just set a new course for that empty region over there, near that blackish, holeish thing. " Zapp Brannigan

 Post subject: Re: Color wheel corrected for human discernment
PostPosted: Fri Dec 12, 2014 8:51 am 
DBB Master
DBB Master
User avatar

Joined: Sun Sep 05, 1999 2:01 am
Posts: 6356
Location: ☃☃☃
One good way to do that if I knew how would be to choose maximally equidistant points in the CIELAB colorspace. It's just not clear to me how to do that though. As a heuristic though, I could, say, add 50% darkness to the odd-numbered colors on the generated rainbow, and see where that goes.

Display posts from previous:  Sort by  
Post new topic  Reply to topic  [ 5 posts ] 

All times are UTC-06:00

Who is online

Users browsing this forum: No registered users and 1 guest

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  

Descent'rs have piloted these pages
The layout and contents contained within this site are © DescentBB.net 1997-2006.
Descent, Descent II are © Parallax Software Corporation.
Descent III is Outrage Entertainment.
Descent is a Trademark of Interplay Productions.

Miner Wars™ is trademark of Keen Software House s. r. o.

Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group