<p align="center">
<a href="https://github.com/nschloe/colorio"><img alt="colorio" src="https://raw.githubusercontent.com/nschloe/colorio/assets/colorio-logo.svg" width="60%"></a>
<p align="center">Tools for color research.</p>
</p>
[![PyPi Version](https://img.shields.io/pypi/v/colorio.svg?style=flat-square)](https://pypi.org/project/colorio/)
[![PyPI pyversions](https://img.shields.io/pypi/pyversions/colorio.svg?style=flat-square)](https://pypi.org/project/colorio/)
[![GitHub stars](https://img.shields.io/github/stars/nschloe/colorio.svg?style=flat-square&logo=github&label=Stars&logoColor=white)](https://github.com/nschloe/colorio)
[![Downloads](https://pepy.tech/badge/colorio/month?style=flat-square)](https://pepy.tech/project/colorio)
<!--[![PyPi downloads](https://img.shields.io/pypi/dm/colorio.svg?style=flat-square)](https://pypistats.org/packages/colorio)-->
[![Discord](https://img.shields.io/static/v1?logo=discord&logoColor=white&label=chat&message=on%20discord&color=7289da&style=flat-square)](https://discord.gg/hnTJ5MRX2Y)
### Installation
Install colorio [from PyPI](https://pypi.org/project/colorio/) with
```
pip install colorio
```
### Illuminants, observers, white points
| Illuminants | CIE 1931 Observer |
| :-----------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------: |
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/illuminants.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/cie-standard-observer-2.svg" width="100%"> |
```python
import colorio
import matplotlib.pyplot as plt
illu = colorio.illuminants.d65()
plt.plot(illu.lmbda_nm, illu.data)
plt.xlabel("wavelength [nm]")
plt.show()
```
The following illuminants are provided:
- Illuminant A ("indoor light", `colorio.illuminants.a(resolution_in_nm)`)
- Illuminant C (obsolete, "North sky daylight", `colorio.illuminants.c()`)
- Illuminants D ("natural daylight", `colorio.illuminants.d(nominal_temp)` or
`colorio.illuminants.d65()`
etc.)
- Illuminant E (equal energy, `colorio.illuminants.e()`)
- Illuminant series F ("fluorescent lighting", `colorio.illuminants.f2()` etc.)
Observers:
- CIE 1931 Standard 2-degree observer (`colorio.observers.colorio.observers.cie_1931_2()`)
- CIE 1964 Standard 10-degree observer (`colorio.observers.colorio.observers.cie_1964_10()`)
### Color coordinates and spaces
Color coordinates are handled as NumPy arrays or as `ColorCoordinates`, a thin
wrapper around the data that retains the color space information and has some
handy helper methods. Color spaces can be instantiated from the classes in
`colorio.cs`, e.g.,
```python
import colorio
colorio.cs.CIELAB()
```
Most methods that accept such a colorspace also accept a string, e.g.,
`cielab`.
As an example, to interpolate two sRGB colors in OKLAB, and return the sRGB:
```python
from colorio.cs import ColorCoordinates
# you can also plug in large numpy arrays instead of two lists here
c0 = ColorCoordinates([1.0, 1.0, 0.0], "srgb1") # yellow
c1 = ColorCoordinates([0.0, 0.0, 1.0], "srgb1") # blue
# naive interpolation gives [0.5, 0.5, 0.5], a mid gray
# convert to OKLAB
c0.convert("oklab")
c1.convert("oklab")
# interpolate
c2 = (c0 + c1) * 0.5
c2.convert("srgbhex", mode="clip")
print(c2.color_space)
print(c2.data)
```
<!--pytest-codeblocks:expected-output-->
```
<colorio color space sRGB-hex>
#6cabc7
```
All color spaces implement the two methods
<!--pytest-codeblocks:skip-->
```python
vals = colorspace.from_xyz100(xyz)
xyz = colorspace.to_xyz100(vals)
```
for conversion from and to XYZ100. Adding new color spaces is as easy as writing a class
that provides those two methods. The following color spaces are already implemented:
- XYZ (`colorio.cs.XYZ(100)`, the
parameter determining the scaling)
- xyY
(`colorio.cs.XYY(100)`, the parameter determining the scaling of `Y`)
- sRGB (`colorio.cs.SRGBlinear()`,
`colorio.cs.SRGB1()`, `colorio.cs.SRGB255()`, `colorio.cs.SRGBhex()`)
- HSL and HSV (`colorio.cs.HSL()`,
`colorio.cs.HSV()`)
These classes have the two methods
```
from_srgb1()
to_srgb1()
```
for conversion from and to standard RGB.
- OSA-UCS (`colorio.cs.OsaUcs()`)
- CIELAB (`colorio.cs.CIELAB()`)
- CIELUV (`colorio.cs.CIELUV()`)
- RLAB (`colorio.cs.RLAB()`)
- DIN99 and its variants DIN99{b,c,d} (`colorio.cs.DIN99()`)
- ICtCp (`colorio.cs.ICtCp()`)
- IPT (`colorio.cs.IPT()`)
- CIECAM02 / CAM02-UCS
```python
import math
import colorio
ciecam02 = colorio.cs.CIECAM02(0.69, 20, 100)
cam02 = colorio.cs.CAM02("UCS", 0.69, 20, 100)
```
The implementation contains a few improvements over the CIECAM02 specification (see
[here](https://arxiv.org/abs/1802.06067)).
- CAM16 / CAM16-UCS
```python
import math
import colorio
cam16 = colorio.cs.CAM16(0.69, 20, 100)
cam16ucs = colorio.cs.CAM16UCS(0.69, 20, 100)
```
The implementation contains a few improvements over the CAM16
specification (see [here](https://arxiv.org/abs/1802.06067)).
- [J<sub>z</sub>a<sub>z</sub>b<sub>z</sub>](https://doi.org/10.1364/OE.25.015131)
(`colorio.cs.JzAzBz()`)
- Oklab (`colorio.cs.OKLAB()`)
- proLab (`colorio.cs.PROLAB()`)
- SRLAB2 (`colorio.cs.SRLAB2()`)
All methods in colorio are fully vectorized, i.e., computation is _really_ fast.
### Color difference formulas
colorio implements the following color difference formulas:
- CIE76
<!--pytest-codeblocks:skip-->
```python
colorio.diff.cie76(lab1, lab2)
```
- CIE94
<!--pytest-codeblocks:skip-->
```python
colorio.diff.cie94(lab1, lab2)
```
- CIEDE2000
<!--pytest-codeblocks:skip-->
```python
colorio.diff.ciede2000(lab1, lab2)
```
- CMC l:c
<!--pytest-codeblocks:skip-->
```python
colorio.diff.cmc(lab1, lab2)
```
### Chromatic adaptation transforms
colorio implements the following CATs:
- von Kries
<!--pytest-codeblocks:skip-->
```python
cat, cat_inv = colorio.cat.von_kries(whitepoint_source, whitepoint_destination)
xyz1 = cat @ xyz0
```
- Bradford (`colorio.cat.bradford`)
- sharp (`colorio.cat.sharp`)
- CMCCAT2000 (`colorio.cat.cmccat2000`)
- CAT02 (`colorio.cat.cat02`)
- CAT16 (`colorio.cat.cat16`)
- Bianco-Schettini (`colorio.cat.bianco_schettini`)
### Gamut visualization
colorio provides a number of useful tools for analyzing and visualizing color spaces.
#### sRGB gamut
| CIELAB | CAM16-UCS | Oklab |
| :-----------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------: |
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/srgb-gamut-cielab.png" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/srgb-gamut-cam16.png" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/srgb-gamut-oklab.png" width="100%"> |
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/srgb-gamut-slice-cielab.png" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/srgb-gamut-slice-cam16ucs.png" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/srgb-gamut-slice-oklab.png" width="100%"> |
The sRGB gamut is a perfect cube in sRGB space, and takes curious shapes when translated
into other color spaces. The above images show the sRGB gamut in different color spaces.
<!--pytest-codeblocks:skip-->
```python
import colorio
p = colorio.plot_rgb_gamut(
"cielab", # or colorio.cs.CIELAB()
n=51,
show_grid=True,
)
p.show()
```
For more visualization options, you can store the sRGB data in a file
```python
import colorio
colorio.save_rgb_gamut("srgb.vtk", "cielab", n=51)
# all formats supported by https://github.com/nschloe/meshio
```
and open it with a tool of your choice. See
[here](https://github.com/nschloe/colorio/wiki/Visualizing-VTK-files) for how to open
the file in [ParaView](https://www.paraview.org/).
For lightness slices of the sRGB gamut, use
<!--pytest-codeblocks:skip-->
```python
import colorio
p = colorio.plot_rgb_slice("cielab", lightness=50.0, n=51)
p.show()
# or
# p.screenshot("screenshot.png")
```
#### Surface color gamut
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/surface-gamut-xyz.png" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/surface-gamut-cielab.png" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/surface-gamut-cam16.png" width="100%"> |
| :-----------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------: |
| XYZ | CIELAB | CAM16-UCS |
Same as above, but with the surface color gamut visible under a given illuminant.
<!--pytest-codeblocks:skip-->
```python
import colorio
illuminant = colorio.illuminants.d65()
observer = colorio.observers.cie_1931_2()
p = colorio.plot_surface_gamut(
"xyz100", # or colorio.cs.XYZ(100)
observer,
illuminant,
)
p.show()
```
The gamut is shown in grey since sRGB screens are not able to display the colors anyway.
#### The visible gamut
| xyY | JzAzBz | Oklab |
| :-----------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------: |
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/visible-gamut-xyy.png" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/visible-gamut-jzazbz.png" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/visible-gamut-oklab.png" width="100%"> |
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/visible-gamut-slice-xyy.png" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/visible-gamut-slice-jzazbz.png" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/visible-gamut-slice-oklab.png" width="100%"> |
Same as above, but with the gamut of visible colors up to a given lightness `Y`.
<!--pytest-codeblocks:skip-->
```python
import colorio
observer = colorio.observers.cie_1931_2()
colorspace = colorio.cs.XYZ(100)
p = colorio.plot_visible_gamut(colorspace, observer, max_Y1=1)
p.show()
```
The gamut is shown in grey since sRGB screens are not able to display the colors anyway.
For slices, use
```python
import colorio
plt = colorio.plot_visible_slice("cielab", lightness=0.5)
plt.show()
```
### Color gradients
With colorio, you can easily visualize the basic color gradients of any color space.
This may make defects in color spaces obvious, e.g., the well-known blue-distortion of
CIELAB and related spaces. (Compare with [the hue linearity data
below](#hue-linearity).)
```python
import colorio
plt = colorio.plot_primary_srgb_gradients("cielab")
plt.show()
```
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/gradients-cielab.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/gradients-din99.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/gradients-oklab.svg" width="100%"> |
| :----------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------: |
| CIELAB | DIN99 | OKLAB |
### Experimental data
colorio contains lots of experimental data sets some of which can be used to assess
certain properties of color spaces. Most data sets can also be visualized.
#### Color differences
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/macadam1974-xyy1.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/macadam1974-cielab.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/macadam1974-cam16ucs.svg" width="100%"> |
| :----------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------: |
| xyY | CIELAB | CAM16 |
Color difference data from [MacAdam (1974)](https://doi.org/10.1364/JOSA.64.001691). The
above plots show the 43 color pairs that are of comparable lightness. The data is
matched perfectly if the facing line stubs meet in one point.
```python
import colorio
data = colorio.data.MacAdam1974()
cs = colorio.cs.CIELAB
plt = data.plot(cs)
plt.show()
print(colorio.data.MacAdam1974().stress(cs))
```
```
24.54774029343344
```
The same is available for
```
colorio.data.BfdP()
colorio.data.Leeds()
colorio.data.RitDupont()
colorio.data.Witt()
colorio.data.COMBVD() # a weighted combination of the above
```
#### Munsell
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/munsell-xyy1.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/munsell-cielab.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/munsell-cam16ucs.svg" width="100%"> |
| :------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------: |
| xyY | CIELAB | CAM16 |
[Munsell color data](https://www.rit.edu/cos/colorscience/rc_munsell_renotation.php) is
visualized with
```python
import colorio
cs = colorio.cs.CIELUV
plt = colorio.data.Munsell().plot(cs, V=5)
plt.show()
```
To retrieve the Munsell data in xyY format, use
```python
import colorio
munsell = colorio.data.Munsell()
# munsell.h
# munsell.V
# munsell.C
# munsell.xyy
```
#### Ellipses
##### MacAdam ellipses (1942)
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/macadam1942-xyy.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/macadam1942-cielab.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/macadam1942-cam16.svg" width="100%"> |
| :---------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------: |
| xyY (at Y=0.4) | CIELAB (at L=50) | CAM16 (at L=50) |
The famous MacAdam ellipses (from [this
article](https://doi.org/10.1364%2FJOSA.32.000247)) can be plotted with
```python
import colorio
cs = colorio.cs.CIELUV
plt = colorio.data.MacAdam1942(50.0).plot(cs)
plt.show()
```
The better the colorspace matches the data, the closer the ellipses are to circles of
the same size.
##### Luo-Rigg ellipses
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/luo-rigg-xyy.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/luo-rigg-cielab.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/luo-rigg-cam16ucs.svg" width="100%"> |
| :------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------: |
| xyY | CIELAB | CAM16 |
Likewise for [Luo-Rigg](https://doi.org/10.1002/col.5080110107).
```python
import colorio
# xyy = colorio.cs.XYY(100)
# colorio.data.LuoRigg(8).show(xyy, 0.4)
# colorio.data.LuoRigg(8).savefig("luo-rigg-xyy.png", xyy, 0.4)
cieluv = colorio.cs.CIELUV()
plt = colorio.data.LuoRigg(8).plot(cieluv, 50)
plt.show()
```
#### Hue linearity
##### Ebner-Fairchild
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/ebner-fairchild-xyy1.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/ebner-fairchild-cielab.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/ebner-fairchild-cam16ucs.svg" width="100%"> |
| :--------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: |
| xyY | CIELAB | CAM16 |
For example
```python
import colorio
colorspace = colorio.cs.JzAzBz
plt = colorio.data.EbnerFairchild().plot(colorspace)
plt.show()
```
shows constant-hue data from [the Ebner-Fairchild
experiments](https://doi.org/10.1117/12.298269) in the hue-plane of some color spaces.
(Ideally, all colors in one set sit on a line.)
###### Hung-Berns
Likewise for [Hung-Berns](https://doi.org/10.1002/col.5080200506):
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/hung-berns-xyy.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/hung-berns-cielab.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/hung-berns-cam16.svg" width="100%"> |
| :--------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------: |
| xyY | CIELAB | CAM16 |
Note the dark blue distortion in CIELAB and CAM16.
```python
import colorio
colorspace = colorio.cs.JzAzBz
plt = colorio.data.HungBerns().plot(colorspace)
plt.show()
```
###### Xiao et al.
Likewise for [Xiao et al.](https://doi.org/10.1002/col.20637):
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/xiao-xyy.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/xiao-cielab.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/xiao-cam16.svg" width="100%"> |
| :--------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------: |
| xyY | CIELAB | CAM16 |
```python
import colorio
colorspace = colorio.cs.CIELAB
plt = colorio.data.Xiao().plot(colorspace)
plt.show()
```
#### Lightness
###### Fairchild-Chen
| <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/fairchild-chen-xyy.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/fairchild-chen-cielab.svg" width="100%"> | <img src="https://raw.githubusercontent.com/nschloe/colorio/assets/fairchild-chen-cam16.svg" width="100%"> |
| :------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------: |
| xyY | CIELAB | CAM16 |
Lightness experiment by [Fairchild-Chen](https://doi.org/10.1117/12.872075).
```python
import colorio
cs = colorio.cs.CIELAB
plt = colorio.data.FairchildChen("SL2").plot(cs)
plt.show()
```
### Articles
- [Algorithmic improvements for the CIECAM02 and CAM16 color appearance models, Nico
Schlömer, 2018](https://arxiv.org/abs/1802.06067)
- [On the conversion from OSA-UCS to CIEXYZ, Nico Schlömer,
2019](https://arxiv.org/abs/1911.08323)