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.61North 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):


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 (typically D65 or A).

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 von Kries transform.

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 (D65 or A)
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    # Calculate XYZ for test illuminant
20    X_test = np.sum(spd_test * reflectance * cmf_x) * delta_lambda
21    Y_test = np.sum(spd_test * reflectance * cmf_y) * delta_lambda
22    Z_test = np.sum(spd_test * reflectance * cmf_z) * delta_lambda
23    
24    # Calculate XYZ for reference illuminant
25    X_ref = np.sum(spd_ref * reflectance * cmf_x) * delta_lambda
26    Y_ref = np.sum(spd_ref * reflectance * cmf_y) * delta_lambda
27    Z_ref = np.sum(spd_ref * reflectance * cmf_z) * delta_lambda
28    
29    # Convert to CIE 1960 UCS
30    def xyz_to_uv(X, Y, Z):
31        denom = X + 15*Y + 3*Z
32        u = 4*X / denom if denom != 0 else 0
33        v = 6*Y / denom if denom != 0 else 0
34        return u, v
35    
36    u_test, v_test = xyz_to_uv(X_test, Y_test, Z_test)
37    u_ref, v_ref = xyz_to_uv(X_ref, Y_ref, Z_ref)
38    
39    # Calculate white point
40    X_n_test = np.sum(spd_test * cmf_x) * delta_lambda
41    Y_n_test = np.sum(spd_test * cmf_y) * delta_lambda
42    Z_n_test = np.sum(spd_test * cmf_z) * delta_lambda
43    u_n, v_n = xyz_to_uv(X_n_test, Y_n_test, Z_n_test)
44    
45    # Convert to W*U*V*
46    def uv_to_wuv(u, v, Y, u_n, v_n):
47        W = 25 * (Y ** (1/3)) - 17
48        U = 13 * W * (u - u_n)
49        V = 13 * W * (v - v_n)
50        return W, U, V
51    
52    W_test, U_test, V_test = uv_to_wuv(u_test, v_test, Y_test, u_n, v_n)
53    W_ref, U_ref, V_ref = uv_to_wuv(u_ref, v_ref, Y_ref, u_n, v_n)
54    
55    # Calculate color difference
56    delta_E = np.sqrt((U_test - U_ref)**2 + (V_test - V_ref)**2 + (W_test - W_ref)**2)
57    
58    # Calculate special CRI
59    R_i = 100 - 4.6 * delta_E
60    
61    return R_i
62
63def calculate_cri(spd_test, spd_ref, tcs_reflectances, cmf_x, cmf_y, cmf_z, wavelengths):
64    """
65    Calculate general CRI (Ra) using 8 test color samples
66    
67    Parameters:
68    - spd_test: Test illuminant SPD
69    - spd_ref: Reference illuminant SPD
70    - tcs_reflectances: List of 8 TCS reflectance spectra
71    - cmf_x, cmf_y, cmf_z: Color matching functions
72    - wavelengths: Wavelength array
73    
74    Returns:
75    - Ra: General color rendering index (average of 8 samples)
76    """
77    R_values = []
78    
79    for i, reflectance in enumerate(tcs_reflectances[:8]):
80        R_i = calculate_cri_single_sample(
81            spd_test, spd_ref, reflectance,
82            cmf_x, cmf_y, cmf_z, wavelengths
83        )
84        R_values.append(R_i)
85    
86    # General CRI is the average of 8 special indices
87    Ra = np.mean(R_values)
88    
89    return Ra
90
91# Example usage:
92# wavelengths = np.arange(380, 781, 5)  # 5nm resolution
93# spd_test = load_spd("test_led.csv")
94# spd_ref = load_spd("D65.csv")
95# tcs_reflectances = load_tcs_samples()  # Load 8 TCS samples
96# cmf_x, cmf_y, cmf_z = load_cmf()
97# 
98# cri = calculate_cri(spd_test, spd_ref, tcs_reflectances, cmf_x, cmf_y, cmf_z, wavelengths)
99# 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.187, 100.0, 67.395]),
12    'F7':  np.array([95.044, 100.0, 108.755]),
13    'F11': np.array([100.966, 100.0, 64.370]),
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.187, 100.0, 67.395},
11    "F7":  {95.044, 100.0, 108.755},
12    "F11": {100.966, 100.0, 64.370},
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