Friday, May 22, 2009

Conversion of wavelength in nanometers to RGB in Python

I've been recently doing some calibration/benchmark/demonstration measurements of spectra on an instrument I'm building and thought that it would be cool to visualize the spectra so that you could actually see them in colour. I've also never been able to memorize what colour which wavelength was, so this could even be a self-education project. In other words, the task was to transform wavelength data in nanometers into RGB data.

A short Google search turned up some very informative sites and among them was the algorithm for nanometer to RGB conversion. What seems to be the oldest search result is a conversion algorithm written by Dan Bruton in FORTRAN. You may also be interested in the Color Science site from the same author. As I was a bit confused by the FORTRAN code, I also used what appears to be a translation of this code into C#. I know C# about as much as FORTRAN but the syntax was more understandable to me. My only contribution was a literal translation of the algorithm into Python.

The function takes a value in nanometers and returns a list of [R, G, B] values. Although a PIL putpixel function requires a tuple, I found a list more flexible in case you want to change the values e.g. according to measured intensity. So, here is the code:

def wav2RGB(wavelength):
w = int(wavelength)

# colour
if w >= 380 and w < 440:
R = -(w - 440.) / (440. - 350.)
G = 0.0
B = 1.0
elif w >= 440 and w < 490:
R = 0.0
G = (w - 440.) / (490. - 440.)
B = 1.0
elif w >= 490 and w < 510:
R = 0.0
G = 1.0
B = -(w - 510.) / (510. - 490.)
elif w >= 510 and w < 580:
R = (w - 510.) / (580. - 510.)
G = 1.0
B = 0.0
elif w >= 580 and w < 645:
R = 1.0
G = -(w - 645.) / (645. - 580.)
B = 0.0
elif w >= 645 and w <= 780:
R = 1.0
G = 0.0
B = 0.0
else:
R = 0.0
G = 0.0
B = 0.0

# intensity correction
if w >= 380 and w < 420:
SSS = 0.3 + 0.7*(w - 350) / (420 - 350)
elif w >= 420 and w <= 700:
SSS = 1.0
elif w > 700 and w <= 780:
SSS = 0.3 + 0.7*(780 - w) / (780 - 700)
else:
SSS = 0.0
SSS *= 255

return [int(SSS*R), int(SSS*G), int(SSS*B)]

The output value's range is 0 -- 255. The code could use some streamlining, but even in this form it is fast enough for an occasional image.

Here is whole visible spectrum as made by this function:

... and a line spectrum of our decades-old mercury vapour lamp:

Finally, in case you want to read more about computer colour science:
Rendering spectra
Colour Rendering of Spectra

4 comments:

Paolo said...

http://r-forge.r-project.org/R/?group_id=160
At the link above you can find a collection of R packages that could be of some help for your task, take a look particularly at the spectr* packages.
Hope it helps!

Gregory said...

This is cool. I made it into an online wavelength to RGB converter here.

I put in 600 nm as the default value. What color should that be?

R.L. said...

Paolo - thanks for the link. I'll definitely take a look at this once I got some time on my hands.

Gregory - I'm glad you liked it. I peeked into one of our spectrometers and I would say 600 nm is orange. Pretty close to the header orange bar on this blog's title. A bit different than what MS Paint gives for [255, 176, 0]. Afterall, this algorithm is just an approximation :-) It may also be just my screen...

jamesthenabignumber said...

Hello,

I am interested in inputing a set of intensities accorss the visual spectrum and receiving an output of the RGB colour you would actually see. Do you know of an extension to this code which does such a thing?

Many Thanks,

James Sheils
Physics Teacher
Manchester Grammar School