# ColorData[“VisibleSpectrum”] is wrong?

I am trying to visualize the visible spectrum using the built-in ColorData["VisibleSpectrum"] function which "colors based on light wavelength in nanometers". But I get wrong results for well-known pure colors. For example the yellow color has wavelength of 570–590 nm but ColorData["VisibleSpectrum"][580] returns green:

Is it a bug? How to visualize the visible spectrum in Mathematica correctly?

-
The myriad of color spaces, conversions between them, monitor calibrations, our color perception... there are entire books on this. –  VLC Oct 3 '12 at 14:52
@VLC I understand but should not we expect to get the Yellow color when using such a function with its argument set to yellow wavelength? There already is the built-in color mapping and it works pretty well. –  Alexey Popkov Oct 3 '12 at 14:58
Related: CIE Chromaticity Diagram –  Vitaliy Kaurov Oct 4 '12 at 10:05
The Java source code for generating the diagram in your edit seems to be using a linear interpolation... –  Ｊ. Ｍ. Oct 7 '12 at 13:51
This is still green in version 9. It's not surprising as just changing this built in colour gradient would be annoying for those who used it for graphics, even if it is incorrect. –  Szabolcs Dec 3 '12 at 0:45

(too long for a comment)

Plot[{ColorData["VisibleSpectrum"][x][[1]],
ColorData["VisibleSpectrum"][x][[2]],
ColorData["VisibleSpectrum"][x][[3]]}, {x, 380, 750}, PlotStyle -> {Red, Green, Blue}]


It doesn't seem that you'll be able to obtain Yellow (RGBColor[1, 1, 0]) from ColorData["VisibleSpectrum"]; unfortunately, the docs say nothing about how they're blending the colors to produce "VisibleSpectrum".

Just to make this post less useless, here's a Mathematica implementation of Bruton's conversion algorithm:

brutonIntensity = Interpolation[{{380, 3/10}, {420, 1}, {700, 1}, {780, 3/10}},
InterpolationOrder -> 1];

brutonLambda[x_, γ_: 4/5] := Map[N[brutonIntensity[x] #]^γ &,
Blend[{{0, Magenta}, {3/20, Blue}, {11/40, Cyan}, {13/40, Green}, {1/2, Yellow},
{53/80, Red}, {1, Red}}, Rescale[x, {380, 780}]]] /;
380 <= x <= 780 && 0 < γ <= 1


and an RGB component plot:

I haven't fully investigated the method for converting wavelengths to CIE xyz coordinates; maybe one of these days...

-
What others think that should look like: Approximate RGB values for Visible Wavelengths. Their code allows for RGBColor[1, 1, 0]. –  sebhofer Oct 3 '12 at 16:16
@seb, translating the FORTRAN code to Mathematica would be quite the project... :) –  Ｊ. Ｍ. Oct 3 '12 at 16:23
No, it's really quite easy if you look at this SO question :) It just boils down to defining a piecewise function. I was tempted to do it but I'm quite busy atm. –  sebhofer Oct 3 '12 at 16:33
@J.M. having had experience in translating FORTRAN to Mathematica, it isn't bad. All the constructs are present, including Label and Goto; it just takes a little time. Even better: that code is pretty clean, the code I translated was far from it. –  rcollyer Oct 3 '12 at 16:39
Or more compactly: ListLinePlot[{ColorData["VisibleSpectrum"][#] /. RGBColor -> Sequence} & /@ Range[380, 750] // Transpose, PlotStyle -> {Red, Green, Blue}] –  rm -rf Oct 3 '12 at 21:58

I just had a look at the colours as they are produced on my screen. I have been working with lasers for many (30+) years and can assure you that a 591nm laser line is fairly yellow, around 635nm is fairly red and 488nm appears as cyan, which resembles the colours of the disks well. Are you sure you are not confusing the wavelength of the maximum of black body radiation and its apparent colour with that of single lines? A perfect match of the numbers is not required but their ratios should be close.

With[{colors = {
{Yellow, ColorData["VisibleSpectrum"][591]},
{Cyan, ColorData["VisibleSpectrum"][488]},
{Red, ColorData["VisibleSpectrum"][635]}}
},
({#, Graphics[{#, Disk[]}] & /@ #} & /@ colors)~Flatten~1 // Grid
]


-
“which resembles the colours of the disks well” — looking at your examples, I would actually say it does not match well at all. –  F'x Oct 6 '12 at 13:35
I agree with you; the 488 and 635nm images definitely look quite close to correct. What laser operates at 591nm? I've never used a yellow laser myself except as a by-product (idler wave of an OPO) of producing light around 430nm. –  Oleksandr R. Oct 8 '12 at 9:24
@F'x the proper comparison is not with the approximate descriptions but with the laser lines themselves. I'm pretty sure Ernst just included the discs on the LHS for the benefit of people who don't often work with lasers. –  Oleksandr R. Oct 8 '12 at 9:28
Of course it is only a proper comparison with a laser if the monitor is properly calibrated. For example, if his monitor is set to a lower colour temperature than 6500K, then a colour given in sRGB will look more "red shifted". However, I've now looked in the Mathematica documentation what RGB model Mathematica uses, and it doesn't seem to say anywhere; it's actually just an assumption that it uses sRGB. Of course, the exact RGB values corresponding to frequencies depend on the exact RGB model used. If it is really not documented, the RGB values for a given frequency are not well defined. –  celtschk Oct 8 '12 at 12:26
@Ernst Stelzer Are you sure your retina's cones still differentiate well between "RED" and "GREEN" photons at such light intensities? RED might appear YELLOW if there's LOTS of RED (peaks of M & L cones are quite close...) –  adrienlucca.wordpress.com Aug 11 '13 at 20:36

That's a good question. There does seem to be a considerable discrepancy versus the 1931 CIE diagram:

GraphicsGrid @ List @ Table[
Graphics @ {ColorData["VisibleSpectrum"][i], Disk[], White, Text[i]},
{i, 380, 700, 10}
]


Perhaps there was a miscalculation made in reducing the very large CIE color space values to sRGB triplets? sRGB, the standard display space, is the inset triangle below:

-
I was about to ask you if you created the diagrams yourself... than I took a look at wikipedia :) –  sebhofer Oct 3 '12 at 14:54
@sebhofer No, I'm not that knowledgeable in the subject. –  Mr.Wizard Oct 3 '12 at 14:55
@seb: it should not be too hard to reproduce these diagrams in Mathematica, tho. :) –  Ｊ. Ｍ. Oct 3 '12 at 15:38
@J.M. the tricky part is the tick marks along the surface of the region. –  rcollyer Oct 3 '12 at 15:56
@J.M. I suspected that much but I was to timid to ask for it directly ;) –  sebhofer Oct 3 '12 at 15:59

This is a bug (or an imperfection) of ColorData["VisibleSpectrum"].

Others have delved into its root, but in my answer I simply will make it clear by a comparison with an experimental, known single wavelength color[1]: sodium’s D-line at 589.0 nm.

Here is Mathematica’s idea of the color of this wavelength, using default settings (no color profile conversion applied, default MMA settings on my MacBook Pro, Mac OS X 10.8.2):

Anyone who has observed the sodium D-line during as a student will know that it doesn't have that greenish hue seen above. Now, here are spectroscopic observations of this same color, for those who have never seen it:

In conclusion: sure, color conversions are tricky… but here, Mathematica is well outside the margin of error :)

[1] Well, almost single color… the two lines are not that far apart.

-
Nice! Did you take those interferograms yourself? –  Ｊ. Ｍ. Oct 6 '12 at 13:53
@J.M. no, I did not (links are given in my answer)… I have set up these two types of interferometers in the past, but I never took pictures. Also, once you have the Michelson set up, it is much more beautiful to gaze at white light fringes than monochrome ones :) –  F'x Oct 6 '12 at 13:58
Perhaps I'm missing your point, but comparison with digital photographs made using the Na D-line is not going to give meaningful results; one should compare instead with actual sodium light. I tend to agree with Ernst; Mathematica's idea of what 514 or 532nm radiation look like is quite close to what they really do look like from a laser (less saturated in the former case, admittedly, but the sRGB color space is missing a huge chunk in this region, so that's not too surprising). –  Oleksandr R. Oct 8 '12 at 9:11
@OleksandrR. I agree: the physical observation with one's eyes are the point I am trying to make, and digital photographs are only here to help as a comparison. But, I can tell from experience that the sodium D-line is not greenish at all, and very unlike Mathematica’s appearance on my screen. The images are much closer, proving that (for this particular wavelength), it's not so much a limitation of the sRGB color space. –  F'x Oct 8 '12 at 9:25
Human perception of yellowish colors changes quite strongly with intensity, and sodium lights are typically quite bright. I agree that there is no green tinge to the Na D-line, but I'm not sure that Mathematica's representation of it looks all that greenish either. So far I'm not convinced as far as the yellow goes, but the other colors seem okay. –  Oleksandr R. Oct 8 '12 at 9:37

The code in the article linked by Alexey produces something similar to this (gradient plot inspired by J.M.'s comment) :

Note though that the description of the code does not seem consistent too me, so I had to change it in some places to get this result. Still I hope that I transcribed it more or less correctly. (I wouldn't use this for scientific purposes though!)

The code that creates the above figure is given below.

rgbSpec[x_] :=
RGBColor@@Piecewise[{
{{19/100 + 19/3000 (410 - x), 0, 1 - 6/300 (410 - x)}, 380 <= x < 410},
{{19/3000 (440 - x), 0, 1}, 410 <= x < 440},
{{0, 1 - (490 - x)/50, 1}, 440 <= x < 490},
{{0, 1, (510 - x)/20}, 490 <= x < 510},
{{1 - (580 - x)/70, 1, 0},510 <= x < 580},
{{1, (640 - x)/60, 0}, 580 <= x < 640},
{{1, 0, 0}, 640 <= x < 700},
{{35/100 + 65/8000 (780 - x), 0, 0}, 700 <= x <= 780}}]

(*rgb components*)
Plot[{rgbSpec[x][[1]], rgbSpec[x][[2]], rgbSpec[x][[3]]}, {x,380,780}, PlotStyle -> {Red, Green, Blue}, PlotRange -> All, Frame -> True]

DensityPlot[x, {x, 380, 780}, {y, 0, 1}, ColorFunction -> (rgbSpec[#] &), ColorFunctionScaling -> False,AspectRatio -> 1/10, FrameTicks -> {{None, None}, {Automatic, None}},Frame -> Automatic, PlotRangePadding -> None]


(If someone actually bothers to check, feel free to correct any mistakes!)

-
Here's a compact version: rgbSpec[x_] := Blend[{{0, RGBColor[19/50, 0, 2/5]}, {3/40, RGBColor[19/100, 0, 1]}, {3/20, Blue}, {11/40, Cyan}, {13/40, Green}, {1/2, Yellow}, {13/20, Red}, {4/5, Red}, {1, RGBColor[7/20, 0, 0]}}, Rescale[x, {380, 780}]]. Here is a gradient plot of the proposed color scheme. –  Ｊ. Ｍ. Oct 5 '12 at 11:37
+1 but please include the code that produces the plot shown, just for completeness. –  Mr.Wizard Oct 5 '12 at 11:37
@Mr.Wizard sorry for that... edited –  sebhofer Oct 5 '12 at 12:10
@J.M. definitely a nicer way to do it! –  sebhofer Oct 5 '12 at 12:10

One hypothesis:

If mathematica's using this set of formulae to convert from XYZ to RGB:

//This is coming from www.easyrgb.com

var_X = X / 100        //X from 0 to  95.047      (Observer = 2°, Illuminant = D65)
var_Y = Y / 100        //Y from 0 to 100.000
var_Z = Z / 100        //Z from 0 to 108.883

var_R = var_X *  3.2406 + var_Y * -1.5372 + var_Z * -0.4986
var_G = var_X * -0.9689 + var_Y *  1.8758 + var_Z *  0.0415
var_B = var_X *  0.0557 + var_Y * -0.2040 + var_Z *  1.0570

if ( var_R > 0.0031308 ) var_R = 1.055 * ( var_R ^ ( 1 / 2.4 ) ) - 0.055
else                     var_R = 12.92 * var_R
if ( var_G > 0.0031308 ) var_G = 1.055 * ( var_G ^ ( 1 / 2.4 ) ) - 0.055
else                     var_G = 12.92 * var_G
if ( var_B > 0.0031308 ) var_B = 1.055 * ( var_B ^ ( 1 / 2.4 ) ) - 0.055
else                     var_B = 12.92 * var_B

R = var_R * 255
G = var_G * 255
B = var_B * 255


AND, if their algorithm tries to normalize the result to have the highest R or G or B value to be equal to 255

AND, if any RGB value below 0 is simply ignored and set to 0

THEN, 570 nm having the CIE xy coordinates:

x = 0.4441
y = 0.5547


Should give, after following these rules, the XYZ values:

X = 72.77  // this is proportionnal to Y
Y = 90.89  // this is to give the value 255 to the G value
Z = 0.2    // this is proportionnal to Y (and extremely low!)


These values give the following rounded RGB values at gamma = 2.4

R = 230
G = 255
B = - 471


Of course, if you cut the - 471 and replace it with 0, that makes quite a difference, let's see what CIE L*a*b* basic delta E color difference formulae will say (the delta E is just the Cartesian distance between 2 points in the CIE L*a*b* colorspace)

We call C the original conversion an C' the 'minus-cut' value

C    L*= 96.367
a*= -26.924
b*= 163.337

C'   L*= 95.305
a*= -31.712
b*= 92.307


The delta E is:

D = sqrt((96.367-95.305)^2+(-26.924+31.712)^2+(163.337-92.307)^2)

D = -71.199   (!!!!)


Knowing that a delta E superior to 1~2 is supposed to be perceptible, we see that here the color difference is supposed to be HUGE

One can argue that a color difference formula applied in this context might not be appropriated, and be right thinking so, but it is still a good illustration of what cutting negative values will produce...

Strangely, the software from EasyRGB is giving the following values, which are more yellowish:

R = 250.46
G = 255
B = 0


Couldn't find why...

**

EDIT:

Anyway,

Never forget that fact that it is simply impossible to display any spectral color on a screen!

Spectral colors in RGB will produce Red/Green/Blue values above 255 or below 0, When it's RED = 380 or -600, the difference between what's displayed and what it should be is SIGNIFICANT.

A better way I know to render the color spectra on a computer screen is to compute a mixture of white light + spectral colors, like this guy Nick Spiker did:

The principle is: white light is added until the negative values become positive or 0 and until the RGB values are below or equal to 255

This rendering gives you a visual result which is close to what spectral lights viewed in daylight conditions look like, and it is more exact than anything else I.M.Opinion.

EDIT 2 Example:

This database is my source for the CIE x,y coordinates of the spctral colors at a resolution of 1 nm (the spectrum locus)

The most simple way to render spectra + white light at the maximal saturation (or highest chroma) level is pick a white light (either illuminant D75, D65, D50, or C, or anything else you have at your disposal...)

I take D65, which has normalized CIE XYZ coordinates:

X = 95.047
Y = 100
Z = 108.883


Then I take all the xy coordinates of the spectrum locus and I transform them in normalized XYZ with the Y value given by the Y colorimetric function multiplied by the D65 curve (so this is the D65 spectrum), and normalized at: Ymax = Y 555nm = 100

Finally, I average the two values (XYZ D65)*a and (XYZ locus)*b, with a, b being weighted such as the RGB conversion of the result will have: min R or/and min G or/and min B = 0 and max R or/and max G or/and max B = 255

That's the basic idea

-
Why did you delete your other answer? (I have not voted for or against either of them; I'm just starting to look.) –  Mr.Wizard Aug 12 '13 at 12:29
@ Mr.Wizard Well, the other answer was out of line since I started from the hypothesis that it wasn't a bug, before figuring out it should be one... I will update this answer with relevant information from the other, best. –  adrienlucca.wordpress.com Aug 12 '13 at 15:22
It's polite to include a link to the source that you took the image from: repairfaq.org/sam/repspec –  Rahul Aug 12 '13 at 16:19
I didn't follow the "A better way I know to render the color spectra on a computer screen is ..." part. Are you planning to give an example of that rendering? –  Mr.Wizard Aug 12 '13 at 22:13
@Rahul I added that link; thanks. –  Mr.Wizard Aug 12 '13 at 22:16