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.
| Illuminant | CCT | $X_n$ | $Y_n$ | $Z_n$ | Description |
|---|---|---|---|---|---|
| D50 | 5003K | 96.42 | 100.0 | 82.51 | Horizon daylight, printing standard |
| D55 | 5503K | 95.68 | 100.0 | 92.15 | Mid-morning/afternoon daylight |
| D65 | 6504K | 95.047 | 100.0 | 108.883 | Noon daylight, sRGB standard |
| D75 | 7504K | 94.97 | 100.0 | 122.61 | North sky daylight |
A-Series (Incandescent)
| Illuminant | CCT | $X_n$ | $Y_n$ | $Z_n$ | Description |
|---|---|---|---|---|---|
| A | 2856K | 109.85 | 100.0 | 35.58 | Tungsten incandescent lamp |
F-Series (Fluorescent)
| Illuminant | CCT | $X_n$ | $Y_n$ | $Z_n$ | Description |
|---|---|---|---|---|---|
| F2 | 4230K | 99.19 | 100.0 | 67.39 | Cool white fluorescent |
| F7 | 6500K | 95.04 | 100.0 | 108.75 | Daylight fluorescent |
| F11 | 4000K | 100.96 | 100.0 | 64.35 | Narrow-band white fluorescent |
E (Equal Energy)
| Illuminant | CCT | $X_n$ | $Y_n$ | $Z_n$ | Description |
|---|---|---|---|---|---|
| E | 5454K | 100.0 | 100.0 | 100.0 | Theoretical 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
- Always specify the illuminant when measuring or specifying colors
- D65 is most common for digital imaging and displays
- D50 is standard for print and graphic arts
- Chromatic adaptation is necessary when converting between illuminants
- Observer angle (2° or 10°) also affects XYZ values
- CRI is illuminant-specific - always compare against appropriate reference
Further Reading
- Standard Illuminant - Wikipedia
- CIE Illuminants
- Bruce Lindbloom's Chromatic Adaptation
- Color Rendering Index - CIE 13.3
Related Snippets
- CIE LAB Color Space
Perceptually uniform color space for color difference calculations - CIE XYZ Color Space
Device-independent color representation based on human vision - Color Difference Formulas
Delta E and perceptual color distance calculations - Color Space Conversions - Quick Reference
Quick reference for all color space conversion formulas - HSV/HSL Color Spaces
Intuitive color representations for user interfaces and color pickers - RGB Color Space & Gamma Correction
sRGB color model and gamma encoding/decoding formulas