Python implementations to convert spectra to observed colour using CIE 1931 and sRGB colourspaces.
/Users/eway/.pyenv/versions/3.8.3/lib/python3.8/site-packages/pandas/compat/__init__.py:97: UserWarning: Could not import the lzma module. Your installed Python is incomplete. Attempting to use lzma compression will result in a RuntimeError.
  warnings.warn(msg)

class Blackbody[source]

Blackbody(T_K, λ_nm=None)

Computes blackbody spectra for a given temperature.

Blackbody.spec_intensity[source]

Blackbody.spec_intensity(λ_nm:scalar or 1D array)

Spectral Intensity from Planck's law

Blackbody.plot[source]

Blackbody.plot()

show blackbody spectra

For example, this will plot the blackbody spectrum at 6500 K using the default wavelength range.

bb = Blackbody(6500)
bb.plot()

Change Blackbody.λ_nm or make a new object Blackbody(temp,wavelengths) to change the internal settings.

bb
Blackbody temperature = 6500 K
Provided nm wavelength input = #(61) [400 405 410 415 420 425 430 435 440 445 450 455 460 465 470 475 480 485
 490 495 500 505 510 515 520 525 530 535 540 545 550 555 560 565 570 575
 580 585 590 595 600 605 610 615 620 625 630 635 640 645 650 655 660 665
 670 675 680 685 690 695 700]

You can also index the Blackbody object like an array. This will give you a tuple of the wavelengths at that index and the blackblody specific intensity

bb[0:3]
(array([400, 405, 410]),
 array([4.61387428e+13, 4.64389680e+13, 4.66994810e+13]))

Analytical Approximation to CIE XYZ

piecewise_Guass[source]

piecewise_Guass(x, α, μ, σ1, σ2)

wavelength2xyz[source]

wavelength2xyz(λ)

λ is in nanometers

plot_cie_xyz_matching_functions[source]

plot_cie_xyz_matching_functions()

plot_cie_xyz_matching_functions()

Since the XYZ colour space has more colours than what sRGB can represent, there will be some RGB values that are negative. To "fix" this, you can use mode=CLAMP to zero any negative values or mode=WHITEN to inflate all values to be positive and then rescaling it back to the range from zero to original max value.

xyz2sRGB[source]

xyz2sRGB(xyz, mode='WHITEN')

converts xyz to sRGB. mode can be 'WHITEN' or 'CLAMP'

test_eq(wavelength2xyz(500),[0.0022533296481400955, 0.32735772154968634, 0.27144376385475233] )
test_eq(xyz2sRGB([0.00225333, 0.32735772, 0.27144376],mode="WHITEN"),[0,160,108])

plot_colour[source]

plot_colour(sRGB)

plot_colour(xyz2sRGB(wavelength2xyz(580)))

sRGB2hex[source]

sRGB2hex(sRGB)

converts sRGB to hex colour

test_eq(sRGB2hex([0, 160, 108]),"#00A06C")

plot_wavelength_colours[source]

plot_wavelength_colours(mode='WHITEN')

mode can be 'WHITEN' or 'CLAMP'

Here are the two clipping modes. The difference is the whitening mode appears more purple in the ultraviolet region.

plot_wavelength_colours("WHITEN")
plot_wavelength_colours("CLAMP")

Visualisation Hacks for a line of Hyperspectral data

wavelength_intensity2hsv[source]

wavelength_intensity2hsv()

I want to map the invisible UV and NIR to the colour white with saturation depending on intensity. This is unphysical but makes for a nice visualisation of wavelength intensity profile!

wavelength_intensity2hsv()

hsv_map[source]

hsv_map(λ:wavelength nm, I:intensity 0-1=1.0)

You may want to use a single hue to define a colour palette. The following pure_hue_palette will create a palette from black to saturated colour depending on the wavelength. These palettes can be used in Bokeh plots.

pure_hue_palette[source]

pure_hue_palette(λ:wavelength nm, n:palette size >= 3=16)

pure_hue_palette(λ=450,n=8)
['#000000',
 '#000524',
 '#000A48',
 '#000F6D',
 '#001491',
 '#0019B6',
 '#001EDA',
 '#0023FF']

I was able to use these palettes in HoloViews to colour in grayscale images given the wavelength. Try the slider so iterate through different wavelengths.

import holoviews as hv
hv.notebook_extension("bokeh",logo=False)

img = cv2.imread("assets/test_img.jpeg", cv2.IMREAD_GRAYSCALE)
wavelengths = np.linspace(380,800,num=16)

def coloured_img(img,λ):
    return hv.Image(img).opts(aspect="equal", cmap=pure_hue_palette(λ=λ,n=16),colorbar=True)

img_dict = {λ:coloured_img(img,λ) for λ in wavelengths}
hmap = hv.HoloMap(img_dict, kdims='wavelength (nm)')
hv.output(hmap, widget_location="bottom")

plot_hsv_LUT_spectrum[source]

plot_hsv_LUT_spectrum()

The UV and NIR regions should show as white if your spectrometer detects something at that frequency. Black is reserved for no light.

plot_hsv_LUT_spectrum()

colour_hyperspectral_line[source]

colour_hyperspectral_line(λs, img)

λs is the index for x axis, y axis is the cross track dimension

Load in a hyperspectral line taken using a pushbroom sensor. img2 is a flat field

img2 = Image.open("assets/flat_pic.png")
img2 = np.asarray(img2,dtype=np.float64)
img2 /= np.max(img2)

plt.imshow(img2,cmap='gray',extent=[400,850,0,np.shape(img2)[0]])
plt.xlabel("wavelengths (nm)")
plt.ylabel("cross-track")
plt.colorbar()
<matplotlib.colorbar.Colorbar at 0x122d74340>
colour_hyperspectral_line(np.linspace(400,850,2064),img2)
100%|██████████| 772/772 [00:31<00:00, 24.29it/s]

img3 is a picture of the spectral lines from a HgAr spectral source.

img3 = Image.open("assets/HgAr_pic.png")
img3 = np.asarray(img3,dtype=np.float64)
img3 /= np.max(img3)
    
plt.imshow(img3,cmap='gray',extent=[400,850,0,np.shape(img3)[0]])
plt.xlabel("wavelengths (nm)")
plt.ylabel("cross-track")
plt.colorbar()
<matplotlib.colorbar.Colorbar at 0x122e7bd90>
colour_hyperspectral_line(np.linspace(400,850,2064),img3)
100%|██████████| 772/772 [00:37<00:00, 20.69it/s]

spectra2sRGB[source]

spectra2sRGB(λs, Is:1D spectrum array)

hyperspec_line2colour[source]

hyperspec_line2colour(λs, img)

This grabs a line of hyperspectral data and returns a column of the observed colour cross-track

hyperspec_line2colour(np.linspace(400,850,2064),img2)
100%|██████████| 772/772 [01:08<00:00, 11.23it/s]