Standard Illuminants & Chromatic Adaptation

Reference light sources used in colorimetry for consistent color measurement and reproduction.

Overview

Standard illuminants define the spectral power distribution (SPD) of light sources. They're essential for:

  • Color measurement and specification
  • Chromatic adaptation (adjusting colors for different lighting)
  • Color matching across devices
  • Quality control in manufacturing
  • Color rendering index (CRI) calculations

Common CIE Standard Illuminants

D-Series (Daylight)

Represent natural daylight at different color temperatures.

IlluminantCCT$X_n$$Y_n$$Z_n$Description
D505003K96.42100.082.51Horizon daylight, printing standard
D555503K95.68100.092.15Mid-morning/afternoon daylight
D656504K95.047100.0108.883Noon daylight, sRGB standard
D757504K94.97100.0122.64North sky daylight

A-Series (Incandescent)

IlluminantCCT$X_n$$Y_n$$Z_n$Description
A2856K109.85100.035.58Tungsten incandescent lamp

F-Series (Fluorescent)

IlluminantCCT$X_n$$Y_n$$Z_n$Description
F24230K99.19100.067.39Cool white fluorescent
F76500K95.04100.0108.75Daylight fluorescent
F114000K100.96100.064.35Narrow-band white fluorescent

E (Equal Energy)

IlluminantCCT$X_n$$Y_n$$Z_n$Description
E5454K100.0100.0100.0Theoretical equal energy

Spectral Power Distributions

Illuminant SPDs at 5nm resolution (380-780nm):

xychart-beta
    title "CIE Standard Illuminants - Spectral Power Distribution"
    x-axis "Wavelength (nm)" [380, 385, 390, 395, 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, 705, 710, 715, 720, 725, 730, 735, 740, 745, 750, 755, 760, 765, 770, 775, 780]
    y-axis "Relative Power" 0 --> 140
    line "D65 (6504K)" [49.98, 52.31, 54.65, 68.70, 82.75, 87.12, 91.49, 92.46, 93.43, 90.06, 86.68, 95.77, 104.86, 110.94, 117.01, 117.41, 117.81, 116.34, 114.86, 115.39, 115.92, 112.37, 108.81, 109.08, 109.35, 108.58, 107.80, 106.30, 104.79, 106.24, 107.69, 106.05, 104.41, 104.23, 104.05, 102.02, 100.00, 98.17, 96.33, 96.06, 95.79, 92.24, 88.69, 89.35, 90.01, 89.80, 89.60, 88.65, 87.70, 85.49, 83.29, 83.49, 83.70, 81.86, 80.03, 80.12, 80.21, 81.25, 82.28, 80.28, 78.28, 74.00, 69.72, 70.67, 71.61, 72.98, 74.35, 67.98, 61.60, 65.74, 69.89, 72.49, 75.09, 69.34, 63.59, 55.01, 46.42, 56.61, 66.81, 65.09, 63.38]
    line "D50 (5003K)" [24.49, 27.18, 29.87, 39.59, 49.32, 52.91, 56.51, 58.27, 60.04, 58.93, 57.82, 66.32, 74.82, 81.04, 87.26, 88.93, 90.61, 90.99, 91.37, 93.24, 95.11, 93.54, 91.97, 93.84, 95.72, 96.17, 96.62, 96.87, 97.13, 99.61, 102.09, 101.43, 100.78, 101.54, 102.30, 101.16, 100.00, 98.87, 97.74, 98.33, 98.93, 96.21, 93.50, 95.59, 97.69, 98.48, 99.27, 99.16, 99.04, 97.38, 95.72, 97.29, 98.86, 97.70, 96.54, 97.48, 98.42, 100.00, 101.58, 100.75, 99.92, 97.13, 94.35, 96.90, 99.45, 102.02, 104.58, 99.19, 93.80, 98.81, 103.82, 107.69, 111.55, 106.86, 102.16, 91.99, 81.83, 98.82, 115.80, 115.39, 114.98]
    line "A (2856K)" [9.80, 10.90, 12.09, 13.35, 14.71, 16.15, 17.68, 19.29, 20.99, 22.79, 24.67, 26.64, 28.70, 30.85, 33.09, 35.41, 37.81, 40.30, 42.87, 45.52, 48.24, 51.04, 53.91, 56.85, 59.86, 62.93, 66.06, 69.25, 72.50, 75.80, 79.15, 82.55, 86.00, 89.49, 93.02, 96.59, 100.00, 103.83, 107.69, 111.57, 115.47, 119.36, 123.27, 127.18, 131.09, 135.00, 138.90, 142.79, 146.67, 150.54, 154.38, 158.21, 162.02, 165.80, 169.55, 173.27, 176.97, 180.63, 184.25, 187.84, 191.38, 194.89, 198.35, 201.77, 205.14, 208.46, 211.74, 214.97, 218.15, 221.28, 224.36, 227.39, 230.36, 233.28, 236.15, 238.96, 241.72, 244.42, 247.07, 249.66, 252.19]

Color Temperature (CCT)

Correlated Color Temperature is measured in Kelvin (K):

  • < 3000K: Warm (yellowish) - Candles, incandescent
  • 3000-5000K: Neutral - Fluorescent, morning sun
  • > 5000K: Cool (bluish) - Daylight, overcast sky

Approximation Formula

For Planckian radiators (blackbody):

$$ \text{CCT} \approx -449n^3 + 3525n^2 - 6823.3n + 5520.33 $$

Where $n = \frac{x - 0.3320}{y - 0.1858}$ and $(x, y)$ are chromaticity coordinates.


Chromatic Adaptation

When converting colors between different illuminants, chromatic adaptation is needed.

Bradford Transform (Most Accurate)

The Bradford transform converts XYZ values from one illuminant to another through a cone response domain (LMS):

Step 1: Convert XYZ to cone response (LMS)

$$ \begin{bmatrix} L \\\\ M \\\\ S \end{bmatrix} = \mathbf{M}_{\text{Bradford}} \cdot \begin{bmatrix} X \\\\ Y \\\\ Z \end{bmatrix} $$

Step 2: Scale cone responses

$$ \begin{bmatrix} L' \\\\ M' \\\\ S' \end{bmatrix} = \begin{bmatrix} \frac{L_{\text{dest}}}{L_{\text{src}}} & 0 & 0 \\\\ 0 & \frac{M_{\text{dest}}}{M_{\text{src}}} & 0 \\\\ 0 & 0 & \frac{S_{\text{dest}}}{S_{\text{src}}} \end{bmatrix} \cdot \begin{bmatrix} L \\\\ M \\\\ S \end{bmatrix} $$

Step 3: Convert back to XYZ

$$ \begin{bmatrix} X' \\\\ Y' \\\\ Z' \end{bmatrix} = \mathbf{M}_{\text{Bradford}}^{-1} \cdot \begin{bmatrix} L' \\\\ M' \\\\ S' \end{bmatrix} $$

Where:

$$ \mathbf{M}_{\text{Bradford}} = \begin{bmatrix} 0.8951 & 0.2664 & -0.1614 \\\\ -0.7502 & 1.7135 & 0.0367 \\\\ 0.0389 & -0.0685 & 1.0296 \end{bmatrix} $$

Color Rendering Index (CRI)

CRI measures how accurately a light source renders colors compared to a reference illuminant of the same CCT (a Planckian radiator below 5000K, CIE daylight at or above 5000K).

CRI Calculation Method

Step 1: Select 8 test color samples (TCS01-TCS08)

These are standardized Munsell color samples covering the hue circle.

Step 2: Calculate tristimulus values under test and reference illuminants

For each test sample $i$:

$$ X_i^{\text{test}}, Y_i^{\text{test}}, Z_i^{\text{test}} = \int S_{\text{test}}(\lambda) R_i(\lambda) \bar{x}(\lambda) d\lambda, \ldots $$$$ X_i^{\text{ref}}, Y_i^{\text{ref}}, Z_i^{\text{ref}} = \int S_{\text{ref}}(\lambda) R_i(\lambda) \bar{x}(\lambda) d\lambda, \ldots $$

Step 3: Convert to CIE 1960 UCS (u, v) coordinates

$$ u = \frac{4X}{X + 15Y + 3Z} $$$$ v = \frac{6Y}{X + 15Y + 3Z} $$

Step 4: Apply chromatic adaptation

Adapt test illuminant colors to reference illuminant using the von Kries transform in CIE 1960 $(c, d)$ space, with $c = (4 - u - 10v)/v$ and $d = (1.708v + 0.404 - 1.481u)/v$. Each illuminant is normalized so its white has $Y = 100$, and each uses its own white point.

Step 5: Convert to CIE 1964 WUV* color space

$$ W^* = 25Y^{1/3} - 17 $$$$ U^* = 13W^*(u - u_n) $$$$ V^* = 13W^*(v - v_n) $$

Step 6: Calculate color difference for each sample

$$ \Delta E_i = \sqrt{(U^*_{test} - U^*_{ref})^2 + (V^*_{test} - V^*_{ref})^2 + (W^*_{test} - W^*_{ref})^2} $$

Step 7: Calculate special color rendering index for each sample

$$ R_i = 100 - 4.6\Delta E_i $$

Step 8: Calculate general CRI (Ra)

$$ \text{CRI (Ra)} = \frac{1}{8}\sum_{i=1}^{8} R_i $$

CRI Interpretation

  • CRI > 90: Excellent (museum, art gallery, medical)
  • CRI 80-90: Good (home, office, retail)
  • CRI 70-80: Fair (industrial, outdoor)
  • CRI < 70: Poor (street lighting, parking)

Python Implementation

  1import numpy as np
  2
  3def calculate_cri_single_sample(spd_test, spd_ref, reflectance, cmf_x, cmf_y, cmf_z, wavelengths):
  4    """
  5    Calculate CRI for a single test color sample
  6    
  7    Parameters:
  8    - spd_test: Test illuminant SPD
  9    - spd_ref: Reference illuminant SPD (Planckian at test CCT if < 5000K, else CIE daylight)
 10    - reflectance: Test color sample reflectance
 11    - cmf_x, cmf_y, cmf_z: CIE 1931 color matching functions
 12    - wavelengths: Wavelength array
 13    
 14    Returns:
 15    - R_i: Special color rendering index for this sample
 16    """
 17    delta_lambda = wavelengths[1] - wavelengths[0]
 18    
 19    def xyz(spd, refl):
 20        return np.array([np.sum(spd * refl * cmf) for cmf in (cmf_x, cmf_y, cmf_z)]) * delta_lambda
 21    
 22    # Normalize each illuminant so its own white has Y = 100
 23    white_test = xyz(spd_test, 1.0)
 24    white_ref = xyz(spd_ref, 1.0)
 25    sample_test = xyz(spd_test, reflectance) * 100 / white_test[1]
 26    sample_ref = xyz(spd_ref, reflectance) * 100 / white_ref[1]
 27    
 28    # Convert to CIE 1960 UCS
 29    def xyz_to_uv(X, Y, Z):
 30        denom = X + 15*Y + 3*Z
 31        return 4*X / denom, 6*Y / denom
 32    
 33    def uv_to_cd(u, v):
 34        return (4 - u - 10*v) / v, (1.708*v + 0.404 - 1.481*u) / v
 35    
 36    u_t, v_t = xyz_to_uv(*white_test)
 37    u_r, v_r = xyz_to_uv(*white_ref)
 38    u_i, v_i = xyz_to_uv(*sample_test)
 39    u_ri, v_ri = xyz_to_uv(*sample_ref)
 40    
 41    # Von Kries adaptation of the test sample to the reference white
 42    c_t, d_t = uv_to_cd(u_t, v_t)
 43    c_r, d_r = uv_to_cd(u_r, v_r)
 44    c_i, d_i = uv_to_cd(u_i, v_i)
 45    cc = c_r / c_t * c_i
 46    dd = d_r / d_t * d_i
 47    denom = 16.518 + 1.481*cc - dd
 48    u_ti = (10.872 + 0.404*cc - 4*dd) / denom
 49    v_ti = 5.520 / denom
 50    
 51    # Convert to W*U*V* (both relative to the reference white after adaptation)
 52    def uv_to_wuv(u, v, Y, u_n, v_n):
 53        W = 25 * (Y ** (1/3)) - 17
 54        U = 13 * W * (u - u_n)
 55        V = 13 * W * (v - v_n)
 56        return W, U, V
 57    
 58    W_test, U_test, V_test = uv_to_wuv(u_ti, v_ti, sample_test[1], u_r, v_r)
 59    W_ref, U_ref, V_ref = uv_to_wuv(u_ri, v_ri, sample_ref[1], u_r, v_r)
 60    
 61    # Calculate color difference
 62    delta_E = np.sqrt((U_test - U_ref)**2 + (V_test - V_ref)**2 + (W_test - W_ref)**2)
 63    
 64    # Calculate special CRI
 65    R_i = 100 - 4.6 * delta_E
 66    
 67    return R_i
 68
 69def calculate_cri(spd_test, spd_ref, tcs_reflectances, cmf_x, cmf_y, cmf_z, wavelengths):
 70    """
 71    Calculate general CRI (Ra) using 8 test color samples
 72    
 73    Parameters:
 74    - spd_test: Test illuminant SPD
 75    - spd_ref: Reference illuminant SPD
 76    - tcs_reflectances: List of 8 TCS reflectance spectra
 77    - cmf_x, cmf_y, cmf_z: Color matching functions
 78    - wavelengths: Wavelength array
 79    
 80    Returns:
 81    - Ra: General color rendering index (average of 8 samples)
 82    """
 83    R_values = []
 84    
 85    for i, reflectance in enumerate(tcs_reflectances[:8]):
 86        R_i = calculate_cri_single_sample(
 87            spd_test, spd_ref, reflectance,
 88            cmf_x, cmf_y, cmf_z, wavelengths
 89        )
 90        R_values.append(R_i)
 91    
 92    # General CRI is the average of 8 special indices
 93    Ra = np.mean(R_values)
 94    
 95    return Ra
 96
 97# Example usage:
 98# wavelengths = np.arange(380, 781, 5)  # 5nm resolution
 99# spd_test = load_spd("test_led.csv")
100# spd_ref = load_spd("reference_at_test_cct.csv")  # Planckian (< 5000K) or CIE daylight
101# tcs_reflectances = load_tcs_samples()  # Load 8 TCS samples
102# cmf_x, cmf_y, cmf_z = load_cmf()
103# 
104# cri = calculate_cri(spd_test, spd_ref, tcs_reflectances, cmf_x, cmf_y, cmf_z, wavelengths)
105# print(f"CRI (Ra): {cri:.1f}")

Practical Implementation

Python

 1import numpy as np
 2
 3# Standard illuminants (2° observer, normalized to Y=100)
 4ILLUMINANTS = {
 5    'A':   np.array([109.850, 100.0, 35.585]),
 6    'D50': np.array([96.422, 100.0, 82.521]),
 7    'D55': np.array([95.682, 100.0, 92.149]),
 8    'D65': np.array([95.047, 100.0, 108.883]),
 9    'D75': np.array([94.972, 100.0, 122.638]),
10    'E':   np.array([100.0, 100.0, 100.0]),
11    'F2':  np.array([99.186, 100.0, 67.393]),
12    'F7':  np.array([95.041, 100.0, 108.747]),
13    'F11': np.array([100.962, 100.0, 64.350]),
14}
15
16def get_illuminant(name='D65'):
17    """Get XYZ values for a standard illuminant"""
18    return ILLUMINANTS.get(name.upper(), ILLUMINANTS['D65'])
19
20# Bradford chromatic adaptation matrix
21BRADFORD = np.array([
22    [ 0.8951,  0.2664, -0.1614],
23    [-0.7502,  1.7135,  0.0367],
24    [ 0.0389, -0.0685,  1.0296]
25])
26
27BRADFORD_INV = np.linalg.inv(BRADFORD)
28
29def chromatic_adaptation(xyz, src_illuminant='D65', dst_illuminant='D50'):
30    """
31    Adapt XYZ color from one illuminant to another using Bradford transform
32    """
33    src_white = get_illuminant(src_illuminant)
34    dst_white = get_illuminant(dst_illuminant)
35    
36    # Convert whites to cone response domain (LMS)
37    src_lms = BRADFORD @ src_white
38    dst_lms = BRADFORD @ dst_white
39    
40    # Convert color to LMS
41    color_lms = BRADFORD @ xyz
42    
43    # Scale factors
44    scale = dst_lms / src_lms
45    
46    # Apply scaling
47    adapted_lms = scale * color_lms
48    
49    # Convert back to XYZ
50    xyz_adapted = BRADFORD_INV @ adapted_lms
51    
52    return xyz_adapted

Go

 1package color
 2
 3var Illuminants = map[string]XYZ{
 4    "A":   {109.850, 100.0, 35.585},
 5    "D50": {96.422, 100.0, 82.521},
 6    "D55": {95.682, 100.0, 92.149},
 7    "D65": {95.047, 100.0, 108.883},
 8    "D75": {94.972, 100.0, 122.638},
 9    "E":   {100.0, 100.0, 100.0},
10    "F2":  {99.186, 100.0, 67.393},
11    "F7":  {95.041, 100.0, 108.747},
12    "F11": {100.962, 100.0, 64.350},
13}
14
15func GetIlluminant(name string) XYZ {
16    if ill, ok := Illuminants[name]; ok {
17        return ill
18    }
19    return Illuminants["D65"] // Default
20}

Use Cases by Illuminant

D65

  • sRGB, Adobe RGB: Standard for computer displays
  • HDTV: Broadcast standard
  • Photography: Digital camera white balance "daylight"

D50

  • Printing: ICC profile connection space
  • ProPhoto RGB: Wide-gamut color space
  • Color management: Industry standard for graphic arts

A

  • Photography: Tungsten/incandescent white balance
  • Testing: Simulating indoor lighting
  • Color rendering index: Reference for warm lighting

F-Series

  • Retail: Simulating store lighting
  • Office: Fluorescent lighting conditions
  • Color rendering: Testing under artificial light

Important Notes

  1. Always specify the illuminant when measuring or specifying colors
  2. D65 is most common for digital imaging and displays
  3. D50 is standard for print and graphic arts
  4. Chromatic adaptation is necessary when converting between illuminants
  5. Observer angle (2° or 10°) also affects XYZ values
  6. CRI is illuminant-specific - always compare against appropriate reference

Further Reading

Related Snippets