I am trying to create a heat map with python. For this I have to assign an RGB value to every value in the range of possible values. I thought of changing the color from blue (minimal value) over green to red (maximal value).
The picture example below explains how I thought of the color composition: We have a range from 1 (pure blue) to 3 (pure red), 2 is in between resembled by green.
I read about linear interpolation and wrote a function that (more or less) handles the calculation for a certain value in the range between a minimum and a maximum and returns an RGB tuple. It uses if
and elif
conditions (which does not make me completely happy):
def convert_to_rgb(minimum, maximum, value):
minimum, maximum = float(minimum), float(maximum)
halfmax = (minimum + maximum) / 2
if minimum <= value <= halfmax:
r = 0
g = int( 255./(halfmax - minimum) * (value - minimum))
b = int( 255. + -255./(halfmax - minimum) * (value - minimum))
return (r,g,b)
elif halfmax < value <= maximum:
r = int( 255./(maximum - halfmax) * (value - halfmax))
g = int( 255. + -255./(maximum - halfmax) * (value - halfmax))
b = 0
return (r,g,b)
However I wonder if one could write a function for each color value without using if
conditions. Does anybody have an idea? Thank you a lot!
It really comes down to math and getting a value between 0-1. Since 255 is the maximum value, dividing by 255 expresses a 0-1 representation. Each channel (Red, Green, and Blue are each channels) is 8 bits, so they are each limited to 256, in this case 255 since 0 is included.
Each level is represented by the range of decimal numbers from 0 to 255 (256 levels for each color), equivalent to the range of binary numbers from 00000000 to 11111111, or hexadecimal 00 to FF. The total number of available colors is 256 x 256 x 256, or 16,777,216 possible colors.
The function R*0.2126+ G*0.7152+ B*0.0722 is said to calculate the perceived brightness (or equivalent grayscale color) for a given an RGB color. Assuming we use the interval [0,1] for all RGB values, we can calculate the following: yellow = RGB(1,1,0) => brightness=0.9278.
def rgb(minimum, maximum, value):
minimum, maximum = float(minimum), float(maximum)
ratio = 2 * (value-minimum) / (maximum - minimum)
b = int(max(0, 255*(1 - ratio)))
r = int(max(0, 255*(ratio - 1)))
g = 255 - b - r
return r, g, b
Here's another way to do it that, while not as absolutely short as possible, is much more general since it hasn't been hardcoded for your specific set of colors. This means it can also be used to linearly interpolate a specified range of values over a variably-sized palette of arbitrary colors.
Also note that colors could have been interpolated in other colorspaces giving results that may be more pleasing than in others. This is illustrated in the different results obtained from the two separate answers I submitted to a related question titled Range values to pseudocolor.
import sys
EPSILON = sys.float_info.epsilon # Smallest possible difference.
def convert_to_rgb(minval, maxval, val, colors):
# `colors` is a series of RGB colors delineating a series of
# adjacent linear color gradients between each pair.
# Determine where the given value falls proportionality within
# the range from minval->maxval and scale that fractional value
# by the total number in the `colors` palette.
i_f = float(val-minval) / float(maxval-minval) * (len(colors)-1)
# Determine the lower index of the pair of color indices this
# value corresponds and its fractional distance between the lower
# and the upper colors.
i, f = int(i_f // 1), i_f % 1 # Split into whole & fractional parts.
# Does it fall exactly on one of the color points?
if f < EPSILON:
return colors[i]
else: # Return a color linearly interpolated in the range between it and
# the following one.
(r1, g1, b1), (r2, g2, b2) = colors[i], colors[i+1]
return int(r1 + f*(r2-r1)), int(g1 + f*(g2-g1)), int(b1 + f*(b2-b1))
if __name__ == '__main__':
minval, maxval = 1, 3
steps = 10
delta = float(maxval-minval) / steps
colors = [(0, 0, 255), (0, 255, 0), (255, 0, 0)] # [BLUE, GREEN, RED]
print(' Val R G B')
for i in range(steps+1):
val = minval + i*delta
r, g, b = convert_to_rgb(minval, maxval, val, colors)
print('{:.3f} -> ({:3d}, {:3d}, {:3d})'.format(val, r, g, b))
Numeric output:
Val R G B
1.000 -> ( 0, 0, 255)
1.200 -> ( 0, 50, 204)
1.400 -> ( 0, 101, 153)
1.600 -> ( 0, 153, 101)
1.800 -> ( 0, 204, 50)
2.000 -> ( 0, 255, 0)
2.200 -> ( 51, 203, 0)
2.400 -> (102, 152, 0)
2.600 -> (153, 101, 0)
2.800 -> (203, 51, 0)
3.000 -> (255, 0, 0)
Here's the output visualized as a horizontal gradient:
You can often eliminate an if
with an index into an array of two values. Python lacks a ternary conditional operator, but this works:
r = [red_curve_1, red_curve_2][value>=halfmax]
g = [green_curve_1, green_curve_2][value>=halfmax]
b = [blue_curve_1, blue_curve_2][value>=halfmax]
Replace the *_curve_1
and *_curve_2
expressions with the constants or slopes or curves either left or right of the midpoint, respectively.
I'll leave those substitutions to you, but for example:
red_curve_1
and blue_curve_2
are simply 0
green_curve_1
is 255*(value-minimum)/(halfmax-minimum)
"We sense light intensity on a logarithmic scale – an exponential intensity ramp will be seen as a linear ramp" https://courses.cs.washington.edu/courses/cse455/09wi/Lects/lect11.pdf
From the https://en.wikipedia.org/wiki/RGB_color_model: "an input intensity RGB value of (0.5, 0.5, 0.5) only outputs about 22% of full brightness (1.0, 1.0, 1.0), instead of 50%"
This leads to the brownish smudge at 2.5 in @martineau example, where it should be yellow, and cyan at 1.5 in order to get a proper hue gradient.
So the formula you should use to get the gradient is not necessarily what you will want. (sorry for not answering your question directly)
But it might be handy to convert to the HSV or HLS color space model, and use H (for hue) and use that as input, and convert back to RGB for display purposes. ie:
colorsys.hsv_to_rgb(value, 1, 1)
https://docs.python.org/2/library/colorsys.html
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With