colourspace-explorer 1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. colourspace_explorer-1.5/LICENSE +29 -0
  2. colourspace_explorer-1.5/PKG-INFO +79 -0
  3. colourspace_explorer-1.5/README.md +29 -0
  4. colourspace_explorer-1.5/pyproject.toml +27 -0
  5. colourspace_explorer-1.5/setup.cfg +4 -0
  6. colourspace_explorer-1.5/src/colourspace/__init__.py +12 -0
  7. colourspace_explorer-1.5/src/colourspace/convert.py +165 -0
  8. colourspace_explorer-1.5/src/colourspace/gamut/Cmax_res10_full.npy +0 -0
  9. colourspace_explorer-1.5/src/colourspace/gamut/Cmax_res10_sRGB.npy +0 -0
  10. colourspace_explorer-1.5/src/colourspace/gamut/Cmax_res1_full.npy +0 -0
  11. colourspace_explorer-1.5/src/colourspace/gamut/Cmax_res1_sRGB.npy +0 -0
  12. colourspace_explorer-1.5/src/colourspace/gamut/Cmax_res2_full.npy +0 -0
  13. colourspace_explorer-1.5/src/colourspace/gamut/Cmax_res2_sRGB.npy +0 -0
  14. colourspace_explorer-1.5/src/colourspace/gamut.py +367 -0
  15. colourspace_explorer-1.5/src/colourspace/limits/limits_360-780-10_XYZ.npy +0 -0
  16. colourspace_explorer-1.5/src/colourspace/limits/limits_360-780-1_XYZ.npy +0 -0
  17. colourspace_explorer-1.5/src/colourspace/limits/limits_360-780-20_XYZ.npy +0 -0
  18. colourspace_explorer-1.5/src/colourspace/limits/limits_360-780-5_XYZ.npy +0 -0
  19. colourspace_explorer-1.5/src/colourspace/limits.py +304 -0
  20. colourspace_explorer-1.5/src/colourspace/maps.py +607 -0
  21. colourspace_explorer-1.5/src/colourspace/slices.py +251 -0
  22. colourspace_explorer-1.5/src/colourspace/tutorials/1. convert.ipynb +613 -0
  23. colourspace_explorer-1.5/src/colourspace/tutorials/2. sRGB gamut.ipynb +738 -0
  24. colourspace_explorer-1.5/src/colourspace/tutorials/3. full gamut.ipynb +1294 -0
  25. colourspace_explorer-1.5/src/colourspace/tutorials/4. slices.ipynb +1162 -0
  26. colourspace_explorer-1.5/src/colourspace/tutorials/5. cmaps.ipynb +1828 -0
  27. colourspace_explorer-1.5/src/colourspace/tutorials/5.1 cmaps helix.ipynb +691 -0
  28. colourspace_explorer-1.5/src/colourspace/tutorials/copy_tutorials.py +38 -0
  29. colourspace_explorer-1.5/src/colourspace_explorer.egg-info/PKG-INFO +79 -0
  30. colourspace_explorer-1.5/src/colourspace_explorer.egg-info/SOURCES.txt +31 -0
  31. colourspace_explorer-1.5/src/colourspace_explorer.egg-info/dependency_links.txt +1 -0
  32. colourspace_explorer-1.5/src/colourspace_explorer.egg-info/requires.txt +5 -0
  33. colourspace_explorer-1.5/src/colourspace_explorer.egg-info/top_level.txt +1 -0
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Gilles Ferrand
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.1
2
+ Name: colourspace-explorer
3
+ Version: 1.5
4
+ Summary: A package providing tools for working with colours in python
5
+ Author-email: Gilles Ferrand <astro.ferrand@gmail.com>
6
+ License: BSD 3-Clause License
7
+
8
+ Copyright (c) 2025, Gilles Ferrand
9
+ All rights reserved.
10
+
11
+ Redistribution and use in source and binary forms, with or without
12
+ modification, are permitted provided that the following conditions are met:
13
+
14
+ 1. Redistributions of source code must retain the above copyright notice, this
15
+ list of conditions and the following disclaimer.
16
+
17
+ 2. Redistributions in binary form must reproduce the above copyright notice,
18
+ this list of conditions and the following disclaimer in the documentation
19
+ and/or other materials provided with the distribution.
20
+
21
+ 3. Neither the name of the copyright holder nor the names of its
22
+ contributors may be used to endorse or promote products derived from
23
+ this software without specific prior written permission.
24
+
25
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
29
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
33
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+
36
+ Project-URL: Homepage, https://github.com/gillesferrand/colourspace
37
+ Keywords: colour,colour space,colour maps
38
+ Classifier: Development Status :: 5 - Production/Stable
39
+ Classifier: Operating System :: OS Independent
40
+ Classifier: License :: OSI Approved :: BSD License
41
+ Classifier: Programming Language :: Python :: 3
42
+ Requires-Python: >=3.7
43
+ Description-Content-Type: text/markdown
44
+ License-File: LICENSE
45
+ Requires-Dist: numpy
46
+ Requires-Dist: matplotlib
47
+ Requires-Dist: scipy
48
+ Requires-Dist: colorspacious
49
+ Requires-Dist: colour-science
50
+
51
+ This package provides some tools to work with colour in Python in a way that matches human perception. The adopted colour space is CIELCH, the cylindrical representation of CIELAB, with coordinates L = lightness in [0,100], C = chroma >= 0, H = hue angle in [0, 360].
52
+ The package relies on [NumPy](https://numpy.org) for array manipulations, [Matplotlib](https://matplotlib.org) for plotting, and optionally other colour packages listed below.
53
+
54
+ **convert** provides functions to convert a colour triplet between HCL and sRGB spaces, using either built-in [formulas](http://www.easyrgb.com/en/math.php) or wrappers to [colorspacious](https://pypi.python.org/pypi/colorspacious/) or [colour](http://colour-science.org) packages.
55
+
56
+ **gamut** provides functions to compute and visualize the boundary Cmax(L,H) of the sRGB gamut and of the full human gamut. These boundaries can be obtained by two methods: (1) for each point in the LCH plane, convert the colour to the target space (RGB or XYZ) and check whether it falls within the gamut; (2) get the contour points of the gamut in the space where it is naturally defined (RGB or XYZ), and convert them back to LCH space. For the latter method, to resample the function Cmax(L,H) on a regular grid the script relies on [scipy.interpolate](https://docs.scipy.org/doc/scipy/reference/interpolate.html). Both methods assume knowledge of the gamut in some space, this is trivial for the sRGB gamut, not for the human gamut hence the limits script below.
57
+ The pre-computed gamuts at resolutions of 1 and 10 bins per unit L,H are included as Numpy binary files (30 MB).
58
+
59
+ **limits** provides functions to compute and visualize the limits of the human gamut (optimal colour stimuli, a.k.a. MacAdam limits). As explained on [Bruce Lindbloom's website](http://www.brucelindbloom.com/index.html?LabGamutDisplayHelp.html) they are obtained by considering all possible step-functions as spectral distributions. The conversion from light spectrum to XYZ colour is made with [colour](http://colour-science.org) (the computed limits can be compared with the crude triangulation provided by this package). To triangulate the limit surface in 3D the script relies on [scipy.spatial](https://docs.scipy.org/doc/scipy/reference/spatial.html) (this is required by method 1 above).
60
+ The pre-computed limits at resolutions of 1, 5, 10, 20 nm are included as Numpy binary files (4.5 MB).
61
+
62
+ **slices** provides functions to draw slices in the colour space: L-H planes as a function of C (or for the maximum possible C), C-H planes as a function of L, and L-C planes as a function of H.
63
+
64
+ **maps** provides functions to generate colour maps for Matplotlib, and/or written as png images. Three kinds of cmaps are implemented, which separate the different perceptual dimensions: equi-luminant (varying only in H, at the max C), diverging (varying only in C between two hues H1 and H2), and mono-hue (varying only in L, at the maximum C). It includes utility functions for testing a cmap on mock data, and for displaying the path taken by a cmap in the colour space.
65
+
66
+ The package comes with five notebooks, which demonstrate the use of the five scripts.
67
+ The notebook files can be copied in the current directory using function colourspace.copy_tutorials() found in script tutorials/copy_tutorials.py.
68
+ Static renderings of the notebooks can also be seen [on nbviewer](https://nbviewer.org/github/gillesferrand/colourspace/tree/master/src/colourspace/tutorials/):
69
+ [1. Conversions between colour spaces](https://nbviewer.org/github/gillesferrand/colourspace/blob/master/src/colourspace/tutorials/1.%20convert.ipynb)
70
+ [2. Mapping the sRGB gamut](https://nbviewer.org/github/gillesferrand/colourspace/blob/master/src/colourspace/tutorials/2.%20sRGB%20gamut.ipynb)
71
+ [3. Mapping the human gamut](https://nbviewer.org/github/gillesferrand/colourspace/blob/master/src/colourspace/tutorials/3.%20full%20gamut.ipynb)
72
+ [4. Slicing the LCH space](https://nbviewer.org/github/gillesferrand/colourspace/blob/master/src/colourspace/tutorials/4.%20slices.ipynb)
73
+ [5. Making custom colour maps](https://nbviewer.org/github/gillesferrand/colourspace/blob/master/src/colourspace/tutorials/5.%20cmaps.ipynb)
74
+ Additional case study:
75
+ [5.1 Helix colour scheme](https://nbviewer.org/github/gillesferrand/colourspace/blob/master/src/colourspace/tutorials/5.1%20cmaps%20helix.ipynb)
76
+
77
+ ---
78
+ This package is provided "as is", with no warranty of any kind.
79
+ The code can be freely used by whoever wants to explore modern colour theory.
@@ -0,0 +1,29 @@
1
+ This package provides some tools to work with colour in Python in a way that matches human perception. The adopted colour space is CIELCH, the cylindrical representation of CIELAB, with coordinates L = lightness in [0,100], C = chroma >= 0, H = hue angle in [0, 360].
2
+ The package relies on [NumPy](https://numpy.org) for array manipulations, [Matplotlib](https://matplotlib.org) for plotting, and optionally other colour packages listed below.
3
+
4
+ **convert** provides functions to convert a colour triplet between HCL and sRGB spaces, using either built-in [formulas](http://www.easyrgb.com/en/math.php) or wrappers to [colorspacious](https://pypi.python.org/pypi/colorspacious/) or [colour](http://colour-science.org) packages.
5
+
6
+ **gamut** provides functions to compute and visualize the boundary Cmax(L,H) of the sRGB gamut and of the full human gamut. These boundaries can be obtained by two methods: (1) for each point in the LCH plane, convert the colour to the target space (RGB or XYZ) and check whether it falls within the gamut; (2) get the contour points of the gamut in the space where it is naturally defined (RGB or XYZ), and convert them back to LCH space. For the latter method, to resample the function Cmax(L,H) on a regular grid the script relies on [scipy.interpolate](https://docs.scipy.org/doc/scipy/reference/interpolate.html). Both methods assume knowledge of the gamut in some space, this is trivial for the sRGB gamut, not for the human gamut hence the limits script below.
7
+ The pre-computed gamuts at resolutions of 1 and 10 bins per unit L,H are included as Numpy binary files (30 MB).
8
+
9
+ **limits** provides functions to compute and visualize the limits of the human gamut (optimal colour stimuli, a.k.a. MacAdam limits). As explained on [Bruce Lindbloom's website](http://www.brucelindbloom.com/index.html?LabGamutDisplayHelp.html) they are obtained by considering all possible step-functions as spectral distributions. The conversion from light spectrum to XYZ colour is made with [colour](http://colour-science.org) (the computed limits can be compared with the crude triangulation provided by this package). To triangulate the limit surface in 3D the script relies on [scipy.spatial](https://docs.scipy.org/doc/scipy/reference/spatial.html) (this is required by method 1 above).
10
+ The pre-computed limits at resolutions of 1, 5, 10, 20 nm are included as Numpy binary files (4.5 MB).
11
+
12
+ **slices** provides functions to draw slices in the colour space: L-H planes as a function of C (or for the maximum possible C), C-H planes as a function of L, and L-C planes as a function of H.
13
+
14
+ **maps** provides functions to generate colour maps for Matplotlib, and/or written as png images. Three kinds of cmaps are implemented, which separate the different perceptual dimensions: equi-luminant (varying only in H, at the max C), diverging (varying only in C between two hues H1 and H2), and mono-hue (varying only in L, at the maximum C). It includes utility functions for testing a cmap on mock data, and for displaying the path taken by a cmap in the colour space.
15
+
16
+ The package comes with five notebooks, which demonstrate the use of the five scripts.
17
+ The notebook files can be copied in the current directory using function colourspace.copy_tutorials() found in script tutorials/copy_tutorials.py.
18
+ Static renderings of the notebooks can also be seen [on nbviewer](https://nbviewer.org/github/gillesferrand/colourspace/tree/master/src/colourspace/tutorials/):
19
+ [1. Conversions between colour spaces](https://nbviewer.org/github/gillesferrand/colourspace/blob/master/src/colourspace/tutorials/1.%20convert.ipynb)
20
+ [2. Mapping the sRGB gamut](https://nbviewer.org/github/gillesferrand/colourspace/blob/master/src/colourspace/tutorials/2.%20sRGB%20gamut.ipynb)
21
+ [3. Mapping the human gamut](https://nbviewer.org/github/gillesferrand/colourspace/blob/master/src/colourspace/tutorials/3.%20full%20gamut.ipynb)
22
+ [4. Slicing the LCH space](https://nbviewer.org/github/gillesferrand/colourspace/blob/master/src/colourspace/tutorials/4.%20slices.ipynb)
23
+ [5. Making custom colour maps](https://nbviewer.org/github/gillesferrand/colourspace/blob/master/src/colourspace/tutorials/5.%20cmaps.ipynb)
24
+ Additional case study:
25
+ [5.1 Helix colour scheme](https://nbviewer.org/github/gillesferrand/colourspace/blob/master/src/colourspace/tutorials/5.1%20cmaps%20helix.ipynb)
26
+
27
+ ---
28
+ This package is provided "as is", with no warranty of any kind.
29
+ The code can be freely used by whoever wants to explore modern colour theory.
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+ [tool.setuptools]
5
+ package-dir = {"" = "src"}
6
+ [tool.setuptools.package-data]
7
+ colourspace = ["gamut/*.npy","limits/*.npy","tutorials/*.ipynb"]
8
+
9
+ [project]
10
+ name = "colourspace-explorer"
11
+ version = "1.5"
12
+ description = "A package providing tools for working with colours in python"
13
+ readme = "README.md"
14
+ authors = [{ name = "Gilles Ferrand", email = "astro.ferrand@gmail.com" }]
15
+ license = { file = "LICENSE" }
16
+ requires-python = ">=3.7"
17
+ dependencies = ["numpy", "matplotlib", "scipy", "colorspacious", "colour-science"]
18
+ classifiers = [
19
+ "Development Status :: 5 - Production/Stable",
20
+ "Operating System :: OS Independent",
21
+ "License :: OSI Approved :: BSD License",
22
+ "Programming Language :: Python :: 3",
23
+ ]
24
+ keywords = ["colour","colour space","colour maps",]
25
+
26
+ [project.urls]
27
+ "Homepage" = "https://github.com/gillesferrand/colourspace"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,12 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ __author__ = "Gilles Ferrand (U. of Manitoba / RIKEN)"
4
+ __version__ = "1.5"
5
+
6
+ from . import convert
7
+ from . import gamut
8
+ from . import limits
9
+ from . import slices
10
+ from . import maps
11
+
12
+ from .tutorials.copy_tutorials import copy_tutorials
@@ -0,0 +1,165 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Conversion between colour spaces: CIE LCH <-> CIE Lab <-> CIE XYZ <->sRGB
4
+ Can use either:
5
+ - custom formulas taken from http://www.easyrgb.com/en/math.php
6
+ - wrapper to "colorspacious" package from https://pypi.python.org/pypi/colorspacious/
7
+ - wrapper to "colour" package from http://colour-science.org
8
+ """
9
+
10
+ from __future__ import print_function
11
+ import numpy as np
12
+
13
+ convertor = None
14
+ illuminant = None
15
+ LCH2RGB = lambda L,C,H: [np.nan, np.nan, np.nan]
16
+ RGB2LCH = lambda R,G,B: [np.nan, np.nan, np.nan]
17
+
18
+ def set_convertor(name, ill='D65'):
19
+ """ Binds the conversion functions LCH2RGB() and RGB2LCH() to the choosen colour package
20
+ """
21
+ global LCH2RGB, RGB2LCH, convertor, illuminant
22
+ if name not in ['custom', 'colorspacious', 'colourscience']:
23
+ print("Unknown conversion module")
24
+ return
25
+ convertor = name
26
+ illuminant = ill
27
+ if name=='custom':
28
+ LCH2RGB = lambda L,C,H: XYZ2RGB(Lab2XYZ(LCH2Lab((L,C,H))))
29
+ RGB2LCH = lambda R,G,B: Lab2LCH(XYZ2Lab(RGB2XYZ((R,G,B))))
30
+ if name=='colorspacious':
31
+ from colorspacious import cspace_convert
32
+ func_LCH2RGB = lambda L,C,H: cspace_convert([L,C,H], {"name": "CIELCh", "XYZ100_w": ill}, "sRGB1")
33
+ func_RGB2LCH = lambda R,G,B: cspace_convert([R,G,B], "sRGB1", {"name": "CIELCh", "XYZ100_w": ill})
34
+ if name=='colourscience':
35
+ import colour
36
+ if hasattr(colour,'ILLUMINANTS' ): cs_ill = colour.ILLUMINANTS ['CIE 1931 2 Degree Standard Observer'][ill]
37
+ if hasattr(colour,'CCS_ILLUMINANTS'): cs_ill = colour.CCS_ILLUMINANTS['CIE 1931 2 Degree Standard Observer'][ill]
38
+ func_LCH2RGB = lambda L,C,H: colour.XYZ_to_sRGB(colour.Lab_to_XYZ(colour.LCHab_to_Lab([L,C,H]), illuminant=cs_ill))
39
+ func_RGB2LCH = lambda R,G,B: colour.Lab_to_LCHab(colour.XYZ_to_Lab(colour.sRGB_to_XYZ([R,G,B]), illuminant=cs_ill))
40
+ if name=='colorspacious' or name=='colourscience':
41
+ def LCH2RGB(L,C,H):
42
+ if hasattr(L, '__iter__'):
43
+ RGB = np.array(list(map(func_LCH2RGB,L,C,H)))
44
+ R = RGB[:,0]
45
+ G = RGB[:,1]
46
+ B = RGB[:,2]
47
+ else:
48
+ R,G,B = func_LCH2RGB(L,C,H)
49
+ return R, G, B
50
+ def RGB2LCH(R,G,B):
51
+ if hasattr(R, '__iter__'):
52
+ LCH = np.array(list(map(func_RGB2LCH,R,G,B)))
53
+ L = LCH[:,0]
54
+ C = LCH[:,1]
55
+ H = LCH[:,2]
56
+ else:
57
+ L,C,H = func_RGB2LCH(R,G,B)
58
+ return L, C, H
59
+ print("convertor = '%s' (illuminant = '%s')"%(name,illuminant))
60
+
61
+ set_convertor('custom')
62
+
63
+ # cartesian CIE Lab <-> cylindrical CIE LCH
64
+
65
+ def LCH2Lab(LCH):
66
+ L,C,H = LCH
67
+ #L = np.float(L)
68
+ a = C * np.cos(H*2*np.pi/360.)
69
+ b = C * np.sin(H*2*np.pi/360.)
70
+ return (L, a, b)
71
+
72
+ def Lab2LCH(Lab):
73
+ L,a,b = Lab
74
+ #L = np.float(L)
75
+ C = np.sqrt(a**2+b**2)
76
+ H = np.arctan2(b,a) * 360/(2*np.pi)
77
+ H = np.where(H<0, H+360, H)
78
+ return (L, C, H+0.)
79
+
80
+ # perceptual CIE XYZ <-> uniform CIE Lab
81
+
82
+ # standard illuminants for 2° observer
83
+ Xn = {}
84
+ Yn = {}
85
+ Zn = {}
86
+ # D65
87
+ Xn['D65'] = 95.047
88
+ Yn['D65'] = 100.000
89
+ Zn['D65'] = 108.883
90
+ # D50
91
+ Xn['D50'] = 96.422
92
+ Yn['D50'] = 100.000
93
+ Zn['D50'] = 82.521
94
+
95
+ def XYZ2Lab(XYZ):
96
+ X,Y,Z = XYZ
97
+ L = 116 * f_forward(Y/Yn[illuminant]) - 16
98
+ a = 500 * (f_forward(X/Xn[illuminant]) - f_forward(Y/Yn[illuminant]))
99
+ b = 200 * (f_forward(Y/Yn[illuminant]) - f_forward(Z/Zn[illuminant]))
100
+ return (L,a,b)
101
+
102
+ def Lab2XYZ(Lab):
103
+ L,a,b = Lab
104
+ X = Xn[illuminant] * f_reverse((L+16)/116.+a/500.)
105
+ Y = Yn[illuminant] * f_reverse((L+16)/116.)
106
+ Z = Zn[illuminant] * f_reverse((L+16)/116.-b/200.)
107
+ return (X,Y,Z)
108
+
109
+ def f_forward(x):
110
+ return np.where(x > (6/29.)**3, x**(1/3.), 1/3.*(29/6.)**2*x+4/29.)
111
+
112
+ def f_reverse(x):
113
+ return np.where(x > 6/29., x**3, 3*(6/29.)**2*(x-4/29.))
114
+
115
+ # human CIE XYZ <-> machine sRGB
116
+
117
+ RGB_max = 1.
118
+
119
+ def XYZ2RGB(XYZ):
120
+ X,Y,Z = XYZ
121
+ R = RGB_max * gamma_forward( +3.2406 * X/100. -1.5372 * Y/100. -0.4986 * Z/100. )
122
+ G = RGB_max * gamma_forward( -0.9689 * X/100. +1.8758 * Y/100. +0.0415 * Z/100. )
123
+ B = RGB_max * gamma_forward( +0.0557 * X/100. -0.2040 * Y/100. +1.0570 * Z/100. )
124
+ return (R,G,B)
125
+
126
+ def RGB2XYZ(RGB):
127
+ R,G,B = RGB
128
+ X = 100 * ( +0.4124 * gamma_reverse(R/RGB_max) +0.3576 * gamma_reverse(G/RGB_max) +0.1805 * gamma_reverse(B/RGB_max) )
129
+ Y = 100 * ( +0.2126 * gamma_reverse(R/RGB_max) +0.7152 * gamma_reverse(G/RGB_max) +0.0722 * gamma_reverse(B/RGB_max) )
130
+ Z = 100 * ( +0.0193 * gamma_reverse(R/RGB_max) +0.1192 * gamma_reverse(G/RGB_max) +0.9505 * gamma_reverse(B/RGB_max) )
131
+ return (X,Y,Z)
132
+
133
+ def gamma_forward(Cln):
134
+ mask = Cln <= 0.0031308
135
+ g = np.empty_like(Cln)
136
+ g[ mask] = 12.92*Cln[mask]
137
+ g[~mask] = 1.055*(Cln[~mask]**(1/2.4))-0.055
138
+ return g
139
+
140
+ def gamma_reverse(Cnl):
141
+ mask = Cnl <= 0.04045
142
+ g = np.empty_like(Cnl)
143
+ g[ mask] = Cnl[mask]/12.92
144
+ g[~mask] = ((Cnl[~mask]+0.055)/1.055)**2.4
145
+ return g
146
+
147
+ # RGB gamut check
148
+
149
+ def crop1(R,min=0,max=1):
150
+ R_crop = np.where(R < min, np.nan, R )
151
+ R_crop = np.where(R_crop > max, np.nan, R_crop)
152
+ return R_crop
153
+
154
+ def crop3(RGB,min=0,max=1):
155
+ R,G,B = RGB
156
+ return np.stack((crop1(R,min,max),crop1(G,min,max),crop1(B,min,max)),axis=-1)
157
+
158
+ def clip1(R,min=0,max=1):
159
+ R_clip = np.where(R < min, min, R )
160
+ R_clip = np.where(R_clip > max, max, R_clip)
161
+ return R_clip
162
+
163
+ def clip3(RGB,min=0,max=1):
164
+ R,G,B = RGB
165
+ return np.stack((clip1(R,min,max),clip1(G,min,max),clip1(B,min,max)),axis=-1)