15

I have a certain array of floats (in Python) that might range from 0 to 100. I want to create a pseudo-color image so that the colors vary from green (corresponding to 0) to red (100). This is similar to pcolor from matplotlib. However, I do not want to use pcolor.

Is there a function like pseudocolorForValue(val,(minval,maxval)) which returns an RGB triple corresponding to the pseudo-color value for val? Also, is there a flexibility in this function to choose whether to display colors from green-to-red or from red-to-green?

Thanks, Nik

martineau
  • 119,623
  • 25
  • 170
  • 301
Nik
  • 5,515
  • 14
  • 49
  • 75

3 Answers3

18

You could write your own function that converted the values 0…100 → 0…120 degrees and then used that value as the H (or angle) of a color in the HSV (or HLS) colorspace. This could then be converted into an RGB color for display purposes. Linearly interpreted colors often look better when they're calculated in this colorspace: Here's what HSV colorspace looks like:

hsv colorspace diagram

Update:

Good news, I was pleasantly surprised to discover that Python has colorspace conversion routines in its built-in colorsys module (they really mean "batteries included"). What's nice about that is that it makes creating a function that does what I described fairly easy, as illustrated below:

from colorsys import hsv_to_rgb

def pseudocolor(val, minval, maxval):
    """ Convert val in range minval..maxval to the range 0..120 degrees which
        correspond to the colors Red and Green in the HSV colorspace.
    """
    h = (float(val-minval) / (maxval-minval)) * 120

    # Convert hsv color (h,1,1) to its rgb equivalent.
    # Note: hsv_to_rgb() function expects h to be in the range 0..1 not 0..360
    r, g, b = hsv_to_rgb(h/360, 1., 1.)
    return r, g, b

if __name__ == '__main__':
    steps = 10

    print('val       R      G      B')
    for val in range(0, 100+steps, steps):
        print('{:3d} -> ({:.3f}, {:.3f}, {:.3f})'.format(
                                                val, *pseudocolor(val, 0, 100)))

Output:

val       R      G      B
  0 -> (1.000, 0.000, 0.000)
 10 -> (1.000, 0.200, 0.000)
 20 -> (1.000, 0.400, 0.000)
 30 -> (1.000, 0.600, 0.000)
 40 -> (1.000, 0.800, 0.000)
 50 -> (1.000, 1.000, 0.000)
 60 -> (0.800, 1.000, 0.000)
 70 -> (0.600, 1.000, 0.000)
 80 -> (0.400, 1.000, 0.000)
 90 -> (0.200, 1.000, 0.000)
100 -> (0.000, 1.000, 0.000)

Here's a sample showing what its output looks like:

sample showing color interpolation in HSV colorspace

I think you may find the colors generated nicer than in my other answer.

Generalizing:

It's possible to modify this function to be a little more generic in the sense that it will work with colors other then just the Red and Green currently hardcoded into it.

Here's how to do that:

def pseudocolor(val, minval, maxval, start_hue, stop_hue):
    """ Convert val in range minval..maxval to the range start_hue..stop_hue
        degrees in the HSV colorspace.
    """
    h = (float(val-minval) / (maxval-minval)) * (stop_hue-start_hue) + start_hue

    # Convert hsv color (h,1,1) to its rgb equivalent.
    # Note: hsv_to_rgb() function expects h to be in the range 0..1 not 0..360
    r, g, b = hsv_to_rgb(h/360, 1., 1.)
    return r, g, b

if __name__ == '__main__':
    # angles of common colors in hsv colorspace
    RED, YELLOW, GREEN, CYAN, BLUE, MAGENTA = range(0, 360, 60)
    steps = 10

    print('val       R      G      B')
    for val in range(0, 100+steps, steps):
        print('{:3d} -> ({:.3f}, {:.3f}, {:.3f})'.format(
                val, *pseudocolor(val, 0, 100, YELLOW, BLUE)))

Results:

sample showing color interpolation in HSV colorspace between yellow and blue

martineau
  • 119,623
  • 25
  • 170
  • 301
  • Thanks. Interesting suggestion. Though it is hard to believe that a function such as the one I need, is not already built in Python. Afterall, pcolor in matplotlib does the same thing. So it must be calling such a function somehow. Are you aware of such a function? – Nik Jun 05 '12 at 21:59
  • @Nik: No, I'm not aware of a built-in, and frankly am not surprised there isn't one -- it's so domain-specific. The only non-trivial part is the color conversion which you should be able to find in a basic computer graphics book or [online](http://en.wikipedia.org/wiki/HSL_and_HSV#Converting_to_RGB). There's probably an open-source graphics package available with such a utility in it you could use. – martineau Jun 06 '12 at 02:03
  • @Nik: Python has built-in colorspace conversion functions -- see my update to this answer. – martineau Jun 06 '12 at 14:56
  • thanks for the solution - works as a charm. As a side note: I use the code in a case where `val` may be outside the limits of minval, maxval, therefore I added `val = max(val, minval)` and `val = min(val, maxval)`. Also, checking for the equality of minval and maxval is recommended (despite being consenting adults ;-)) – jake77 Jul 19 '16 at 13:16
  • @jake77: Yes, reality is often a special case. btw, the best way to show one's appreciation around here is by upvoting the answer... – martineau Jul 20 '16 at 20:45
7

While arguably not as pretty as interpolating H in the HLS or HSV colorspace, a much simpler to implement approach would be to write a function that mapped the single value into three components corresponding to a linearly-interpolated color between completely red (1,0,0) and completely green (0,1,0) in the RGB colorspace.

Here's what I mean:

def pseudocolor(val, minval, maxval):
    """ Convert value in the range minval...maxval to a color between red
        and green.
    """
    f = float(val-minval) / (maxval-minval)
    r, g, b = 1-f, f, 0.
    return r, g, b

if __name__ == '__main__':
    steps = 10
    print('val       R      G      B')
    for val in xrange(0, 100+steps, steps):
        print('{:3d} -> ({:.3f}, {:.3f}, {:.3f})'.format(
                    val, *pseudocolor(val, 0, 100)))

Output:

val       R      G      B
  0 -> (1.000, 0.000, 0.000)
 10 -> (0.900, 0.100, 0.000)
 20 -> (0.800, 0.200, 0.000)
 30 -> (0.700, 0.300, 0.000)
 40 -> (0.600, 0.400, 0.000)
 50 -> (0.500, 0.500, 0.000)
 60 -> (0.400, 0.600, 0.000)
 70 -> (0.300, 0.700, 0.000)
 80 -> (0.200, 0.800, 0.000)
 90 -> (0.100, 0.900, 0.000)
100 -> (0.000, 1.000, 0.000)

You can transform the floating-point r,g,b components as needed, such as to integers in the range of 0..255.

Here's a sample showing what its output looks like:

image showing interpolation in RGB colorspace

If you want to go from green to red, just reverse the calculations for r and g in the function. Without too much additional effort, you could generalize the concept to allow linear-interpolation between any two given colors.

Here's how that could be done:

def pseudocolor(val, minval, maxval, startcolor, stopcolor):
    """ Convert value in the range minval...maxval to a color in the range
        startcolor to stopcolor. The colors passed and the the one returned are
        composed of a sequence of N component values.
    """
    f = float(val-minval) / (maxval-minval)
    return tuple(f*(b-a)+a for (a, b) in zip(startcolor, stopcolor))

if __name__ == '__main__':
    YELLOW, BLUE = (1, 1, 0), (0, 0, 1)
    steps = 10

    print('val       R      G      B')
    for val in range(0, 100+steps, steps):
        print('{:3d} -> ({:.3f}, {:.3f}, {:.3f})'.format(
                    val, *pseudocolor(val, 0, 100, YELLOW, BLUE)))

Sample output using the provided colors:

image showing interpolation in RGB colorspace with different colors

martineau
  • 119,623
  • 25
  • 170
  • 301
5

You can access matplolib's built-in colormaps directly, which are exactly what pcolor uses to determine its colormapping. Each map takes in a float in the range [0, 1] and returns a 4 element tuple of floats in the range [0, 1] with the components (R, G, B, A). Here is an example of a function which returns an RGBA tuple using the standard jet colormap:

from matplotlib import cm

def pseudocolor(val, minval, maxmal):
    # Scale val to be in the range [0, 1]
    val = (val - minval) / (maxval - minval)
    # Return RGBA tuple from jet colormap
    return cm.jet(val)

pseudocolor(20, 0, 100)
# Returns: (0.0, 0.3, 1.0, 1.0)

pseudocolor(80, 0, 100)
# Returns: (1.0, 0.4074, 0.0, 1.0)

This would map to the color range shown in the image below.

enter image description here

One of the convenient features about this method is that you can easily switch to any one of the matplotlib colormaps by changing cm.jet to cm.rainbow, cm.nipy_spectral, cm.Accent, etc.

Chris Mueller
  • 6,490
  • 5
  • 29
  • 35
  • This deserves more upvotes. Yes, it uses the matplotlib library but it's a short and flexible solution. – medley56 Mar 14 '17 at 17:29