37
votes

I was given a data set that is essentially an image, however each pixel in the image is represented as a value from -1 to 1 inclusive. I am writing an application that needs to take these -1 to 1 grayscale values and map them to the associated RGB value for the MATLAB "Jet" color scale (red-green-blue color gradient).

I am curious if anyone knows how to take a linear value (like -1 to 1) and map it to this scale. Note that I am not actually using MATLAB for this (nor can I), I just need to take the grayscale value and put it on the Jet gradient.

Thanks, Adam

7
So you want to end up with a B&W image encoded with a color format?Pubby
What do the values mean? For example, is -1 pure black?Jim Rhodes
I have a C++ class that converts from a wavelength (in nanometers) to RGB. Is this what you want ? (of course, you would use -1=380nm=violet and 1=780nm=red to cover the whole scale). It seems to me that the 'jet' color scale is more about the visible spectrum than about Red-Green-Blue. Anyway I can post the code if it can help.user948581
The data itself is a 530x530 covariance matrix. Each value of the matrix is represented from -1 to 1 and is traditionally colored with a gradient from dark red (1) to green-sh (0) to dark blue (-1). Here is a link that may help describe the gradient: blogs.mathworks.com/images/loren/73/colormapManip_14.png The data is often used in MATLAB and it can automatically apply this single value to the gradient. However, I need to use it in my real-time C++ graphics application while still preserving the proper color scheme.Adam Shook
Sounds like the most straightforward method would be to calculate hue (in degrees) as something like hue = 120 - 120 * data (to give you a range of 240 = blue to 0 = red) and then assume full saturation and value and convert to RGB. Unless you want to check if (data < 0) then RGB is over one range else if (data > 0) then RGB is over another range.tinman

7 Answers

27
votes

I hope this is what you're looking for:

double interpolate( double val, double y0, double x0, double y1, double x1 ) {
  return (val-x0)*(y1-y0)/(x1-x0) + y0;
}
double blue( double grayscale ) {
  if ( grayscale < -0.33 ) return 1.0;
  else if ( grayscale < 0.33 ) return interpolate( grayscale, 1.0, -0.33, 0.0, 0.33 );
  else return 0.0;
}
double green( double grayscale ) {
  if ( grayscale < -1.0 ) return 0.0; // unexpected grayscale value
  if  ( grayscale < -0.33 ) return interpolate( grayscale, 0.0, -1.0, 1.0, -0.33 );
  else if ( grayscale < 0.33 ) return 1.0;
  else if ( grayscale <= 1.0 ) return interpolate( grayscale, 1.0, 0.33, 0.0, 1.0 );
  else return 1.0; // unexpected grayscale value
}
double red( double grayscale ) {
  if ( grayscale < -0.33 ) return 0.0;
  else if ( grayscale < 0.33 ) return interpolate( grayscale, 0.0, -0.33, 1.0, 0.33 );
  else return 1.0;
}

I'm not sure if this scale is 100% identical to the image you linked but it should look very similar.

UPDATE I've rewritten the code according to the description of MatLab's Jet palette found here

double interpolate( double val, double y0, double x0, double y1, double x1 ) {
    return (val-x0)*(y1-y0)/(x1-x0) + y0;
}

double base( double val ) {
    if ( val <= -0.75 ) return 0;
    else if ( val <= -0.25 ) return interpolate( val, 0.0, -0.75, 1.0, -0.25 );
    else if ( val <= 0.25 ) return 1.0;
    else if ( val <= 0.75 ) return interpolate( val, 1.0, 0.25, 0.0, 0.75 );
    else return 0.0;
}

double red( double gray ) {
    return base( gray - 0.5 );
}
double green( double gray ) {
    return base( gray );
}
double blue( double gray ) {
    return base( gray + 0.5 );
}
80
votes

Consider the following function (written by Paul Bourke -- search for Colour Ramping for Data Visualisation):

/*
   Return a RGB colour value given a scalar v in the range [vmin,vmax]
   In this case each colour component ranges from 0 (no contribution) to
   1 (fully saturated), modifications for other ranges is trivial.
   The colour is clipped at the end of the scales if v is outside
   the range [vmin,vmax]
*/

typedef struct {
    double r,g,b;
} COLOUR;

COLOUR GetColour(double v,double vmin,double vmax)
{
   COLOUR c = {1.0,1.0,1.0}; // white
   double dv;

   if (v < vmin)
      v = vmin;
   if (v > vmax)
      v = vmax;
   dv = vmax - vmin;

   if (v < (vmin + 0.25 * dv)) {
      c.r = 0;
      c.g = 4 * (v - vmin) / dv;
   } else if (v < (vmin + 0.5 * dv)) {
      c.r = 0;
      c.b = 1 + 4 * (vmin + 0.25 * dv - v) / dv;
   } else if (v < (vmin + 0.75 * dv)) {
      c.r = 4 * (v - vmin - 0.5 * dv) / dv;
      c.b = 0;
   } else {
      c.g = 1 + 4 * (vmin + 0.75 * dv - v) / dv;
      c.b = 0;
   }

   return(c);
}

Which, in your case, you would use it to map values in the range [-1,1] to colors as (it is straightforward to translate it from C code to a MATLAB function):

c = GetColour(v,-1.0,1.0);

This produces to the following "hot-to-cold" color ramp:

color_ramp

It basically represents a walk on the edges of the RGB color cube from blue to red (passing by cyan, green, yellow), and interpolating the values along this path.

color_cube


Note this is slightly different from the "Jet" colormap used in MATLAB, which as far as I can tell, goes through the following path:

#00007F: dark blue
#0000FF: blue
#007FFF: azure
#00FFFF: cyan
#7FFF7F: light green
#FFFF00: yellow
#FF7F00: orange
#FF0000: red
#7F0000: dark red

Here is a comparison I did in MATLAB:

%# values
num = 64;
v = linspace(-1,1,num);

%# colormaps
clr1 = jet(num);
clr2 = zeros(num,3);
for i=1:num
    clr2(i,:) = GetColour(v(i), v(1), v(end));
end

Then we plot both using:

figure
subplot(4,1,1), imagesc(v), colormap(clr), axis off
subplot(4,1,2:4), h = plot(v,clr); axis tight
set(h, {'Color'},{'r';'g';'b'}, 'LineWidth',3)

jethot_to_cold

Now you can modify the C code above, and use the suggested stop points to achieve something similar to jet colormap (they all use linear interpolation over the R,G,B channels as you can see from the above plots)...

13
votes

The other answers treat the interpolation as a piecewise linear function. This can be simplified by using a clamped triangular basis function for interpolation. We need a clamp function that maps its input to the closed unit interval:

clamp(x)=max(0, min(x, 1))

And a basis function for interpolation:

N(t) = clamp(1.5 - |2t|)

Then the color becomes:

r = N(t - 0.5), g = N(t), b = N(t + 0.5)

Plotting this from -1 to 1 gives:

Plot of RGB values from -1 to 1

Which is the same as provided in this answer. Using an efficient clamp implementation:

double clamp(double v)
{
  const double t = v < 0 ? 0 : v;
  return t > 1.0 ? 1.0 : t;
}

and ensuring your value t is in [-1, 1], then jet color is simply:

double red   = clamp(1.5 - std::abs(2.0 * t - 1.0));
double green = clamp(1.5 - std::abs(2.0 * t));
double blue  = clamp(1.5 - std::abs(2.0 * t + 1.0));

As shown in the above link on implementing clamp, the compiler may optimize out branches. The compiler may also use intrinsics to set the sign bit for std::abs eliminating another branch.

"Hot-to-Cold"

A similar treatment can be used for the "hot-to-cold" color mapping. In this case the basis and color functions are:

N(t) = clamp(2 - |2t|)

r(t)=N(t-1), g(t) = N(t), b(t) = N(t+1)

And the hot-to-cold plot for [-1, 1]:

Hot-to-cold plot

OpenGL Shader Program

Eliminating explicit branches makes this approach efficient for implementing as an OpenGL shader program. GLSL provides built-in functions for both abs and clamp that operate on 3D vectors. Vectorizing the color calculation and preferring built-in functions over branching can provide significant performance gains. Below is an implementation in GLSL that returns the RGB jet color as a vec3. Note that the basis function was modified such that t must lie in [0,1] rather than the range used in the other examples.

vec3 jet(float t)
{
  return clamp(vec3(1.5) - abs(4.0 * vec3(t) + vec3(-3, -2, -1)), vec3(0), vec3(1));
}
3
votes

I'm not really sure why there are so many complex answers to this simple equation. Based on the MatLab JET Hot-to-Cold color map chart and graph plot posted above in Amro's comment (thank you), the logic is very simple to calculate the RGB values using high-speed/basic math.

I use the following function for live-rendering normalized data to display spectrograms and it's incredibly fast and efficient with no complex math outside double precision multiplication and division, simplified by ternary logic chaining. This code is C# but very easily ported to almost any other language (sorry PHP programmers, you're out of luck thanks to abnormal ternary chain order).

public byte[] GetMatlabRgb(double ordinal)
{
    byte[] triplet = new byte[3];
    triplet[0] = (ordinal < 0.0)  ? (byte)0 : (ordinal >= 0.5)  ? (byte)255 : (byte)(ordinal / 0.5 * 255);
    triplet[1] = (ordinal < -0.5) ? (byte)((ordinal + 1) / 0.5 * 255) : (ordinal > 0.5) ? (byte)(255 - ((ordinal - 0.5) / 0.5 * 255)) : (byte)255;
    triplet[2] = (ordinal > 0.0)  ? (byte)0 : (ordinal <= -0.5) ? (byte)255 : (byte)(ordinal * -1.0 / 0.5 * 255);
    return triplet;
}

The function takes an ordinal range from -1.0 to 1.0 per the JET color specification, though this function does no sanity checking if you're outside that range (I do that before my call here).

So make sure you do sanity/bounds checking prior to calling this function or simply add your own limiting to cap the value when you implement it yourself.

This implementation does not take luminosity into consideration so may not be considered a purist implementation but gets you in the ballpark fairly well and is much faster.

1
votes

Seems like you have hue values of an HSL system and the saturation and lightness are implicit. Search for HSL to RGB conversion on the internet and you will find a lot of explanations, code etc. (Here is one link)

In your particular case, though, let's assume you are defaulting all color saturations to 1 and lightness to 0.5. Here is the formula you can use to get the RGB values:

Imagine for every pixel, you have h the value you read from your data.

hue = (h+1.0)/2;  // This is to make it in range [0, 1]
temp[3] = {hue+1.0/3, hue, hue-1.0/3};
if (temp[0] > 1.0)
    temp[0] -= 1.0;
if (temp[2] < 0.0)
    temp[2] += 1.0;

float RGB[3];
for (int i = 0; i < 3; ++i)
{
    if (temp[i]*6.0 < 1.0)
        RGB[i] = 6.0f*temp[i];
    else if (temp[i]*2.0 < 1.0)
        RGB[i] = 1;
    else if (temp[i]*3.0 < 2.0)
        RGB[i] = ((2.0/3.0)-temp[i])*6.0f;
    else
        RGB[i] = 0;
}

And there you have the RGB values in RGB all in the range [0, 1]. Note that the original conversion is more complex, I simplified it based on values of saturation=1 and lightness=0.5

Why this formula? See this wikipedia entry

1
votes

Java(Processing) code that will generate Jet and HotAndCold RGB. I created this code following the RGB distribution scheme in the post of Amro above.

color JetColor(float v,float vmin,float vmax){
       float r=0, g=0, b=0;
       float x = (v-vmin)/(vmax-vmin);
       r = 255*constrain(-4*abs(x-0.75) + 1.5,0,1);
       g = 255*constrain(-4*abs(x-0.50) + 1.5,0,1);
       b = 255*constrain(-4*abs(x-0.25) + 1.5,0,1);
       return color(r,g,b);
    }

color HeatColor(float v,float vmin,float vmax){
       float r=0, g=0, b=0;
       float x = (v-vmin)/(vmax-vmin);
       r = 255*constrain(-4*abs(x-0.75) + 2,0,1);
       g = 255*constrain(-4*abs(x-0.50) + 2,0,1);
       b = 255*constrain(-4*abs(x) + 2,0,1);
       return color(r,g,b);
    }
    //Values are calculated on trapezoid cutoff points in format y=constrain(a(x-t)+b,0,1)
    //Where a=((delta)y/(delta)x), t=x-offset value to symetric middle of trapezoid, and b=y-a(x-t) for the last peak point (x,y)
0
votes

This probably isn't exactly the same, but it may be close enough for your needs:

if (-0.75 > value) {
    blue = 1.75 + value;
} else if (0.25 > value) {
    blue = 0.25 - value;
} else {
    blue = 0;
}

if ( -0.5 > value) {
    green = 0;
} else if (0.5 > value) {
    green = 1 - 2*abs(value);
} else {
    green = 0;
}

if ( -0.25 > value) {
    red = 0;
} else if (0.75 > value) {
    red = 0.25 + value;
} else {
    red = 1.75 - value;
}