or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

chromatic-adaptation.mdcolor-appearance-models.mdcolor-conversions.mdcolor-diff.mdcolor-objects.mdconstants-standards.mdindex.mdspectral-density.md

chromatic-adaptation.mddocs/

0

# Chromatic Adaptation

1

2

Chromatic adaptation provides low-level functions for transforming colors between different illuminants. This compensates for the human visual system's adaptation to different lighting conditions, ensuring accurate color reproduction across varying illuminants.

3

4

## Capabilities

5

6

### Primary Adaptation Function

7

8

Transform XYZ values from one illuminant to another using various adaptation methods.

9

10

```python { .api }

11

def apply_chromatic_adaptation(val_x, val_y, val_z, orig_illum, targ_illum, adaptation='bradford'):

12

"""

13

Apply chromatic adaptation to XYZ values.

14

15

Parameters:

16

- val_x, val_y, val_z: Original XYZ color values (float)

17

- orig_illum: Original illuminant name (str, e.g., 'd65', 'a', 'c')

18

- targ_illum: Target illuminant name (str, e.g., 'd50', 'd65', 'a')

19

- adaptation: Adaptation method (str, default: 'bradford')

20

Options: 'bradford', 'von_kries', 'xyz_scaling'

21

22

Returns:

23

tuple: Adapted XYZ values (X, Y, Z)

24

25

Raises:

26

- InvalidIlluminantError: If illuminant names are invalid

27

- ValueError: If adaptation method is not supported

28

29

Notes:

30

- Bradford method is most accurate for most applications

31

- Von Kries method is simpler but less accurate

32

- XYZ scaling is the simplest method, least accurate

33

"""

34

```

35

36

### Color Object Adaptation

37

38

Apply chromatic adaptation directly to color objects with automatic illuminant handling.

39

40

```python { .api }

41

def apply_chromatic_adaptation_on_color(color, targ_illum, adaptation='bradford'):

42

"""

43

Apply chromatic adaptation to color object.

44

45

Parameters:

46

- color: Color object (XYZ, Lab, or other illuminant-aware color)

47

- targ_illum: Target illuminant name (str)

48

- adaptation: Adaptation method (str, default: 'bradford')

49

50

Returns:

51

Color object: New color object adapted to target illuminant

52

53

Notes:

54

- Automatically handles color space conversions if needed

55

- Preserves original color object type when possible

56

- Updates illuminant metadata in returned object

57

"""

58

```

59

60

## Supported Adaptation Methods

61

62

### Bradford Method (Recommended)

63

64

The most accurate chromatic adaptation transform for most applications.

65

66

```python

67

# Bradford adaptation (default)

68

adapted_xyz = apply_chromatic_adaptation(

69

val_x=50.0, val_y=40.0, val_z=30.0,

70

orig_illum='d65',

71

targ_illum='d50',

72

adaptation='bradford'

73

)

74

```

75

76

**Characteristics:**

77

- High accuracy across wide range of illuminants

78

- Industry standard for color management

79

- Used in ICC color profiles

80

- Best for most practical applications

81

82

### Von Kries Method

83

84

Classical chromatic adaptation based on cone response.

85

86

```python

87

# Von Kries adaptation

88

adapted_xyz = apply_chromatic_adaptation(

89

val_x=50.0, val_y=40.0, val_z=30.0,

90

orig_illum='d65',

91

targ_illum='a',

92

adaptation='von_kries'

93

)

94

```

95

96

**Characteristics:**

97

- Based on human cone cell responses

98

- Simpler than Bradford method

99

- Good for educational purposes

100

- Less accurate for extreme illuminant changes

101

102

### XYZ Scaling Method

103

104

Simple scaling method for basic adaptation needs.

105

106

```python

107

# XYZ scaling adaptation

108

adapted_xyz = apply_chromatic_adaptation(

109

val_x=50.0, val_y=40.0, val_z=30.0,

110

orig_illum='d65',

111

targ_illum='d50',

112

adaptation='xyz_scaling'

113

)

114

```

115

116

**Characteristics:**

117

- Simplest implementation

118

- Fastest computation

119

- Least accurate results

120

- Only suitable for small illuminant differences

121

122

## Usage Examples

123

124

### Basic Illuminant Adaptation

125

126

```python

127

from colormath.chromatic_adaptation import apply_chromatic_adaptation

128

129

# Adapt color from D65 to D50 illuminant

130

original_xyz = (45.0, 40.0, 35.0)

131

132

adapted_xyz = apply_chromatic_adaptation(

133

val_x=original_xyz[0],

134

val_y=original_xyz[1],

135

val_z=original_xyz[2],

136

orig_illum='d65',

137

targ_illum='d50',

138

adaptation='bradford'

139

)

140

141

print(f"Original XYZ (D65): X={original_xyz[0]:.2f}, Y={original_xyz[1]:.2f}, Z={original_xyz[2]:.2f}")

142

print(f"Adapted XYZ (D50): X={adapted_xyz[0]:.2f}, Y={adapted_xyz[1]:.2f}, Z={adapted_xyz[2]:.2f}")

143

```

144

145

### Color Object Adaptation

146

147

```python

148

from colormath.color_objects import XYZColor, LabColor

149

from colormath.chromatic_adaptation import apply_chromatic_adaptation_on_color

150

from colormath.color_conversions import convert_color

151

152

# Create XYZ color under D65

153

xyz_d65 = XYZColor(xyz_x=45.0, xyz_y=40.0, xyz_z=35.0, illuminant='d65')

154

155

# Adapt to D50 illuminant

156

xyz_d50 = apply_chromatic_adaptation_on_color(xyz_d65, 'd50')

157

158

print(f"Original illuminant: {xyz_d65.illuminant}")

159

print(f"Adapted illuminant: {xyz_d50.illuminant}")

160

print(f"Original XYZ: {xyz_d65.get_value_tuple()}")

161

print(f"Adapted XYZ: {xyz_d50.get_value_tuple()}")

162

163

# Convert both to Lab to see the difference

164

lab_d65 = convert_color(xyz_d65, LabColor)

165

lab_d50 = convert_color(xyz_d50, LabColor)

166

167

print(f"Lab (D65): L={lab_d65.lab_l:.2f}, a={lab_d65.lab_a:.2f}, b={lab_d65.lab_b:.2f}")

168

print(f"Lab (D50): L={lab_d50.lab_l:.2f}, a={lab_d50.lab_a:.2f}, b={lab_d50.lab_b:.2f}")

169

```

170

171

### Comparing Adaptation Methods

172

173

```python

174

from colormath.chromatic_adaptation import apply_chromatic_adaptation

175

176

# Test color

177

test_xyz = (60.0, 50.0, 40.0)

178

orig_illum = 'd65'

179

targ_illum = 'a' # Tungsten illuminant (warm)

180

181

# Compare all adaptation methods

182

methods = ['bradford', 'von_kries', 'xyz_scaling']

183

results = {}

184

185

for method in methods:

186

adapted = apply_chromatic_adaptation(

187

val_x=test_xyz[0],

188

val_y=test_xyz[1],

189

val_z=test_xyz[2],

190

orig_illum=orig_illum,

191

targ_illum=targ_illum,

192

adaptation=method

193

)

194

results[method] = adapted

195

196

print(f"Original XYZ ({orig_illum.upper()}): X={test_xyz[0]:.2f}, Y={test_xyz[1]:.2f}, Z={test_xyz[2]:.2f}")

197

print(f"\nAdapted to {targ_illum.upper()}:")

198

for method, xyz in results.items():

199

print(f" {method:12}: X={xyz[0]:.2f}, Y={xyz[1]:.2f}, Z={xyz[2]:.2f}")

200

201

# Calculate differences from Bradford (reference)

202

bradford_result = results['bradford']

203

print(f"\nDifferences from Bradford:")

204

for method, xyz in results.items():

205

if method != 'bradford':

206

diff_x = abs(xyz[0] - bradford_result[0])

207

diff_y = abs(xyz[1] - bradford_result[1])

208

diff_z = abs(xyz[2] - bradford_result[2])

209

print(f" {method:12}: ΔX={diff_x:.3f}, ΔY={diff_y:.3f}, ΔZ={diff_z:.3f}")

210

```

211

212

### Print Production Workflow

213

214

```python

215

from colormath.color_objects import LabColor, XYZColor

216

from colormath.color_conversions import convert_color

217

from colormath.chromatic_adaptation import apply_chromatic_adaptation_on_color

218

219

def adapt_colors_for_print(colors, source_illuminant='d65', target_illuminant='d50'):

220

"""

221

Adapt colors from display viewing (D65) to print viewing (D50).

222

223

Parameters:

224

- colors: List of color objects

225

- source_illuminant: Source illuminant (typically D65 for displays)

226

- target_illuminant: Target illuminant (typically D50 for print)

227

228

Returns:

229

List of adapted color objects

230

"""

231

adapted_colors = []

232

233

for color in colors:

234

# Convert to XYZ if needed

235

if not isinstance(color, XYZColor):

236

xyz_color = convert_color(color, XYZColor)

237

xyz_color.set_illuminant(source_illuminant)

238

else:

239

xyz_color = color

240

241

# Apply chromatic adaptation

242

adapted_xyz = apply_chromatic_adaptation_on_color(

243

xyz_color, target_illuminant, adaptation='bradford'

244

)

245

246

# Convert back to original color space if needed

247

if not isinstance(color, XYZColor):

248

adapted_color = convert_color(adapted_xyz, type(color))

249

adapted_colors.append(adapted_color)

250

else:

251

adapted_colors.append(adapted_xyz)

252

253

return adapted_colors

254

255

# Example usage

256

display_colors = [

257

LabColor(lab_l=50, lab_a=20, lab_b=30, illuminant='d65'),

258

LabColor(lab_l=60, lab_a=-10, lab_b=40, illuminant='d65'),

259

LabColor(lab_l=40, lab_a=30, lab_b=-20, illuminant='d65')

260

]

261

262

print_colors = adapt_colors_for_print(display_colors)

263

264

print("Display to Print Adaptation:")

265

for i, (display, print_color) in enumerate(zip(display_colors, print_colors)):

266

print(f"Color {i+1}:")

267

print(f" Display (D65): L={display.lab_l:.1f}, a={display.lab_a:.1f}, b={display.lab_b:.1f}")

268

print(f" Print (D50): L={print_color.lab_l:.1f}, a={print_color.lab_a:.1f}, b={print_color.lab_b:.1f}")

269

```

270

271

### Photography White Balance Correction

272

273

```python

274

from colormath.color_objects import XYZColor

275

from colormath.chromatic_adaptation import apply_chromatic_adaptation

276

277

def white_balance_correction(image_colors, shot_illuminant, target_illuminant='d65'):

278

"""

279

Apply white balance correction to image colors.

280

281

Parameters:

282

- image_colors: List of XYZ color tuples from image

283

- shot_illuminant: Illuminant under which photo was taken

284

- target_illuminant: Target illuminant for corrected image

285

286

Returns:

287

List of white balance corrected XYZ tuples

288

"""

289

corrected_colors = []

290

291

for xyz in image_colors:

292

corrected = apply_chromatic_adaptation(

293

val_x=xyz[0],

294

val_y=xyz[1],

295

val_z=xyz[2],

296

orig_illum=shot_illuminant,

297

targ_illum=target_illuminant,

298

adaptation='bradford'

299

)

300

corrected_colors.append(corrected)

301

302

return corrected_colors

303

304

# Example: Correct tungsten lighting to daylight

305

tungsten_colors = [

306

(80.0, 70.0, 30.0), # Warm-tinted color

307

(40.0, 35.0, 15.0), # Another warm color

308

(95.0, 85.0, 40.0) # Bright warm color

309

]

310

311

daylight_colors = white_balance_correction(

312

tungsten_colors,

313

shot_illuminant='a', # Tungsten

314

target_illuminant='d65' # Daylight

315

)

316

317

print("White Balance Correction (Tungsten → Daylight):")

318

for i, (tungsten, daylight) in enumerate(zip(tungsten_colors, daylight_colors)):

319

print(f"Pixel {i+1}:")

320

print(f" Tungsten: X={tungsten[0]:.1f}, Y={tungsten[1]:.1f}, Z={tungsten[2]:.1f}")

321

print(f" Daylight: X={daylight[0]:.1f}, Y={daylight[1]:.1f}, Z={daylight[2]:.1f}")

322

```

323

324

### Color Management Pipeline

325

326

```python

327

from colormath.color_objects import XYZColor, sRGBColor

328

from colormath.color_conversions import convert_color

329

from colormath.chromatic_adaptation import apply_chromatic_adaptation_on_color

330

331

class ColorManagementPipeline:

332

"""Color management pipeline with chromatic adaptation."""

333

334

def __init__(self, source_illuminant='d65', target_illuminant='d50'):

335

self.source_illuminant = source_illuminant

336

self.target_illuminant = target_illuminant

337

338

def process_color(self, color, adaptation_method='bradford'):

339

"""

340

Process color through complete color management pipeline.

341

342

Parameters:

343

- color: Input color object

344

- adaptation_method: Chromatic adaptation method

345

346

Returns:

347

dict: Processed color data

348

"""

349

# Convert to XYZ for chromatic adaptation

350

xyz_original = convert_color(color, XYZColor)

351

xyz_original.set_illuminant(self.source_illuminant)

352

353

# Apply chromatic adaptation

354

xyz_adapted = apply_chromatic_adaptation_on_color(

355

xyz_original,

356

self.target_illuminant,

357

adaptation=adaptation_method

358

)

359

360

# Convert back to sRGB for display

361

rgb_result = convert_color(xyz_adapted, sRGBColor)

362

363

return {

364

'original_xyz': xyz_original.get_value_tuple(),

365

'adapted_xyz': xyz_adapted.get_value_tuple(),

366

'final_rgb': rgb_result.get_value_tuple(),

367

'rgb_hex': rgb_result.get_rgb_hex(),

368

'illuminant_change': f"{self.source_illuminant} → {self.target_illuminant}"

369

}

370

371

# Example usage

372

pipeline = ColorManagementPipeline(source_illuminant='d65', target_illuminant='d50')

373

374

# Process an sRGB color

375

input_color = sRGBColor(rgb_r=0.8, rgb_g=0.4, rgb_b=0.2)

376

result = pipeline.process_color(input_color)

377

378

print("Color Management Pipeline Result:")

379

print(f"Original XYZ: {result['original_xyz']}")

380

print(f"Adapted XYZ: {result['adapted_xyz']}")

381

print(f"Final RGB: {result['final_rgb']}")

382

print(f"Hex color: {result['rgb_hex']}")

383

print(f"Illuminant: {result['illuminant_change']}")

384

```

385

386

### Batch Color Adaptation

387

388

```python

389

from colormath.chromatic_adaptation import apply_chromatic_adaptation

390

391

def batch_adapt_colors(color_list, orig_illum, targ_illum, method='bradford'):

392

"""

393

Apply chromatic adaptation to batch of XYZ colors.

394

395

Parameters:

396

- color_list: List of (X, Y, Z) tuples

397

- orig_illum: Original illuminant

398

- targ_illum: Target illuminant

399

- method: Adaptation method

400

401

Returns:

402

List of adapted (X, Y, Z) tuples

403

"""

404

adapted_colors = []

405

406

for xyz in color_list:

407

adapted = apply_chromatic_adaptation(

408

val_x=xyz[0],

409

val_y=xyz[1],

410

val_z=xyz[2],

411

orig_illum=orig_illum,

412

targ_illum=targ_illum,

413

adaptation=method

414

)

415

adapted_colors.append(adapted)

416

417

return adapted_colors

418

419

# Example: Adapt color palette from A illuminant to D65

420

palette_a = [

421

(85.0, 75.0, 25.0), # Warm yellow

422

(45.0, 40.0, 60.0), # Cool blue

423

(70.0, 35.0, 35.0), # Red

424

(25.0, 45.0, 30.0), # Green

425

(90.0, 90.0, 85.0) # Near white

426

]

427

428

palette_d65 = batch_adapt_colors(palette_a, 'a', 'd65')

429

430

print("Palette Adaptation (Illuminant A → D65):")

431

for i, (orig, adapted) in enumerate(zip(palette_a, palette_d65)):

432

print(f"Color {i+1}: ({orig[0]:5.1f}, {orig[1]:5.1f}, {orig[2]:5.1f}) → ({adapted[0]:5.1f}, {adapted[1]:5.1f}, {adapted[2]:5.1f})")

433

```

434

435

## Technical Details

436

437

### Adaptation Matrix Structure

438

439

Each adaptation method uses specific transformation matrices:

440

441

```python

442

# Bradford adaptation matrices (example)

443

BRADFORD_M = [

444

[0.8951, 0.2664, -0.1614],

445

[-0.7502, 1.7135, 0.0367],

446

[0.0389, -0.0685, 1.0296]

447

]

448

449

BRADFORD_M_INV = [

450

[0.9869929, -0.1470543, 0.1599627],

451

[0.4323053, 0.5183603, 0.0492912],

452

[-0.0085287, 0.0400428, 0.9684867]

453

]

454

```

455

456

### Supported Illuminants

457

458

All standard CIE illuminants are supported:

459

- **D series**: D50, D55, D65, D75 (daylight illuminants)

460

- **A**: Tungsten incandescent (2856K)

461

- **B**: Direct sunlight (obsolete)

462

- **C**: Average daylight (obsolete)

463

- **E**: Equal energy illuminant

464

- **F series**: Fluorescent illuminants

465

466

### Performance Characteristics

467

468

| Method | Speed | Accuracy | Use Case |

469

|--------|-------|----------|----------|

470

| Bradford | Fast | High | General purpose |

471

| Von Kries | Fast | Medium | Educational, simple cases |

472

| XYZ Scaling | Fastest | Low | Quick approximations |

473

474

### Error Handling

475

476

```python

477

from colormath.color_exceptions import InvalidIlluminantError

478

479

try:

480

result = apply_chromatic_adaptation(50, 40, 30, 'invalid', 'd65')

481

except InvalidIlluminantError as e:

482

print(f"Invalid illuminant: {e}")

483

```

484

485

Common errors:

486

- **InvalidIlluminantError**: Unknown illuminant name

487

- **ValueError**: Unsupported adaptation method

488

- **TypeError**: Invalid color values