# A computational framework for colour metrics and colour space transforms

- Published
- Accepted
- Received

- Academic Editor
- Klara Kedem

- Subject Areas
- Computer Vision, Graphics, Optimization Theory and Computation, Scientific Computing and Simulation, Software Engineering
- Keywords
- Colour metrics, Colour space, Transform, Object-oriented, Python

- Copyright
- © 2016 Farup
- Licence
- This is an open access article distributed under the terms of the Creative Commons Attribution License, which permits unrestricted use, distribution, reproduction and adaptation in any medium and for any purpose provided that it is properly attributed. For attribution, the original author(s), title, publication source (PeerJ Computer Science) and either DOI or URL of the article must be cited.

- Cite this article
- 2016. A computational framework for colour metrics and colour space transforms. PeerJ Computer Science 2:e48 https://doi.org/10.7717/peerj-cs.48

## Abstract

An object-oriented computational framework for the transformation of colour data and colour metric tensors is presented. The main idea of the design is to represent the transforms between spaces as compositions of objects from a class hierarchy providing the methods for both the transforms themselves and the corresponding Jacobian matrices. In this way, new colour spaces can be implemented on the fly by transforming from any existing colour space, and colour data in various formats as well as colour metric tensors and colour difference data can easily be transformed between the colour spaces. This reduces what normally requires several days of coding to a few lines of code without introducing a significant computational overhead. The framework is implemented in the Python programming language.

## Introduction

Colour data such as measured colours, specified colours or pixels of colour images are most commonly described as sets of points in a three-dimensional space—a so-called colour space. Many different colour spaces are currently in use in various settings. For many applications, selecting the best colour space for processing the data can be crucial (Plataniotis & Venetsanopoulos, 2000). Converting between all the different colour spaces can be challenging. Different conventions for scaling and normalisation is used, and many of the colour spaces commonly in use are inaccurately defined. The complexity of conversion is particularly present for computations involving colour metric data, which, by nature, is tensorial (Deza & Deza, 2009), giving rise to the need for not only the direct transformations, but also the corresponding Jacobian matrices—a tedious and error-prone process (Pant & Farup, 2012). So far, no common framework for such transformations of colour data and metrics including the automated computation of Jacobian matrices has been constructed.

From other fields of computational science, it is well established that object-oriented frameworks can be useful for simplifying such matters (Beall & Shephard, 1999). With the advent of modern high-level interpreted languages, the computational overhead is not nearly as high as before, and the ease of use has increased significantly (Cai, Langtangen & Moe, 2005). Thus, in order to simplify the matters for colour science and engineering, an object-oriented framework for colour space construction, and conversion of colour data and colour metric tensor data is designed. The framework is currently limited to three-dimensional colour spaces.

Following the background material on the principles of transforming colour data and related tensorial data in the following section, the principles and ideas underlying the framework are presented. To demonstrate to which degree the framework simplifies the implementation of colour data and metric transformations, an implementation of the framework using the high-level programming language Python (Van Rossum & Drake, 1995) is applied to some standard example problems.

## Background

### Transformation of colour data

Transformations between different colour spaces can in general take the shape of a function, $\stackrel{\u0304}{x}=\stackrel{\u0304}{x}\left(x\right)$, where *x* = (*x*_{1}, *x*_{2}, *x*_{3})^{T} represents a colour, i.e., a point in a colour space. Fortunately, most common colour space conversions are made up of a small set of relatively simple mathematical operations. The linear transformation is a very common ingredient in the transforms. Some colour spaces, such as, e.g., the CIECAT02 colour adaptation space (Moroney et al., 2002), are even defined simply by a linear transformation from some other colour space: (1)$\stackrel{\u0304}{x}=Ax,$
where *A* is a 3 × 3 constant matrix. Combined with the so-called gamma correction, which is applied channel-wise, most RGB type colour spaces, and also, e.g., the IPT (Ebner & Fairchild, 1998) colour space can be construced (2)${\stackrel{\u0304}{x}}_{i}=\mathrm{sgn}\left({x}_{i}\right)|{x}_{i}{|}^{\gamma},$
where *γ* > 0 is the constant exponent.

For many perceptual colour spaces such as CIELAB, both Cartesian and cylindrical coordinates are commonly used for describing the chromatic plane. The transformation from Cartesian to polar is (3)$\begin{array}{c}{\stackrel{\u0304}{x}}_{1}={x}_{1},\hfill \\ {\stackrel{\u0304}{x}}_{2}=\sqrt{{x}_{2}^{2}+{x}_{3}^{2}},\hfill \\ {\stackrel{\u0304}{x}}_{3}=\mathrm{atan2}\left({x}_{3},{x}_{2}\right),\hfill \end{array}$
with the corresponding inverse transform (4)$\begin{array}{c}{\stackrel{\u0304}{x}}_{1}={x}_{1},\hfill \\ {\stackrel{\u0304}{x}}_{2}={x}_{2}cos\left({x}_{3}\right),\hfill \\ {\stackrel{\u0304}{x}}_{3}={x}_{2}sin\left({x}_{3}\right).\hfill \end{array}$
Chromaticities and luminances are often represented in projective spaces such as xyY, (5)$\begin{array}{c}{\stackrel{\u0304}{x}}_{1}=\frac{{x}_{1}}{{x}_{1}+{x}_{2}+{x}_{3}},\hfill \\ {\stackrel{\u0304}{x}}_{2}=\frac{{x}_{2}}{{x}_{1}+{x}_{2}+{x}_{3}},\hfill \\ {\stackrel{\u0304}{x}}_{3}={x}_{2}.\hfill \end{array}$
Colour spaces used for colour metrics such as Δ*E*_{E} (Oleari, Melgosa & Huertas, 2009) and the various DIN99 metrics (Cui et al., 2002) often include a logarithmic compression of some or all of the channels such as lightness and chroma (radius in polar coordinates): (6)${\stackrel{\u0304}{x}}_{i}={a}_{i}ln\left(1+{b}_{i}{x}_{i}\right),$
where *a*_{i} and *b*_{i} are the parameters of the transform. Recently, the Poincaré disk representation of the hyperbolic plane has been used for representing the chromatic plane (Lenz, Carmona & Meer, 2007; Farup, 2014). The chroma-preserving mapping to the Poincaré disk can be written as a mapping of the radius in polar coordinates as (7)${\stackrel{\u0304}{x}}_{2}=tanh\left(\frac{{x}_{2}}{2R}\right),$
where *R* > 0 is the radius of curvature. Besides these more generic transformations, various non-linear transformation functions specific to individual colour spaces are used in such cases as sRGB, CIELAB, CIELUV, the underlying colour space of the CIEDE2000 metric, etc.

### Transformation of tensorial data

Most colour metrics can be represented in the form of a line element, or a differential quadratic form (Wyszecki & Stiles, 1982, Chapter 8.4), as (8)$d{s}^{2}=d{x}^{T}\mathrm{G}dx.$ Here, G is the metric tensor—a function of the coordinates. For metrics defined as Euclidean distances in a given colour space, the metric tensor is the identity tensor, I, in the given space. Some colour metrics, like, e.g., CIEDE2000, cannot be written in this form, but can be linearised—or Riemannised—to a good approximation (Pant & Farup, 2012).

Under a coordinate transformation, $\stackrel{\u0304}{x}=\stackrel{\u0304}{x}\left(x\right)$, this metric transforms according to (9)$d{s}^{2}=d{x}^{T}\mathrm{G}dx=d{\stackrel{\u0304}{x}}^{T}{\frac{\partial x}{\partial \stackrel{\u0304}{x}}}^{T}\mathrm{G}\frac{\partial x}{\partial \stackrel{\u0304}{x}}d\stackrel{\u0304}{x}=d{\stackrel{\u0304}{x}}^{T}\stackrel{\u0304}{\mathrm{G}}d\stackrel{\u0304}{x},$ where $\partial x\u2215\partial \stackrel{\u0304}{x}$ is the Jacobian matrix of the coordinate transform with componentns $\partial {x}_{i}\u2215\partial {\stackrel{\u0304}{x}}_{j}$. In other words, the metric tensor transforms according to (10)$\stackrel{\u0304}{\mathrm{G}}={\frac{\partial x}{\partial \stackrel{\u0304}{x}}}^{T}\mathrm{G}\frac{\partial x}{\partial \stackrel{\u0304}{x}}.$ Under composition of several coordinate transformations, $\stackrel{\u0303}{x}=\stackrel{\u0303}{x}\left(\stackrel{\u0304}{x}\right)=\stackrel{\u0303}{x}\left(\stackrel{\u0304}{x}\left(x\right)\right)$, the process is nested, (11)$d{s}^{2}=d{\stackrel{\u0303}{x}}^{T}{\frac{\partial \stackrel{\u0304}{x}}{\partial \stackrel{\u0303}{x}}}^{T}{\frac{\partial x}{\partial \stackrel{\u0304}{x}}}^{T}\mathrm{G}\frac{\partial x}{\partial \stackrel{\u0304}{x}}\frac{\partial \stackrel{\u0304}{x}}{\partial \stackrel{\u0303}{x}}d\stackrel{\u0303}{x},$ (12)$\stackrel{\u0303}{\mathrm{G}}={\frac{\partial \stackrel{\u0304}{x}}{\partial \stackrel{\u0303}{x}}}^{T}{\frac{\partial x}{\partial \stackrel{\u0304}{x}}}^{T}\mathrm{G}\frac{\partial x}{\partial \stackrel{\u0304}{x}}\frac{\partial \stackrel{\u0304}{x}}{\partial \stackrel{\u0303}{x}},$ which can also be seen directly from the chain rule for the Jacobian matrices, (13)$\frac{\partial x}{\partial \stackrel{\u0303}{x}}=\frac{\partial x}{\partial \stackrel{\u0304}{x}}\frac{\partial \stackrel{\u0304}{x}}{\partial \stackrel{\u0303}{x}}.$

All the points with unit distance from a given central point—a unit ball—constitute an ellipsoid (14)$\Delta {x}^{T}\mathrm{G}\Delta x=1.$
The cross section of this ellipsoid with a principal plane in a given coordinate is obtained by setting the corresponding Δ*x*_{i} = 0, reducing the ellipsoid to an ellipsis (Pant & Farup, 2012), (15)$\left(\begin{array}{cc}\hfill \Delta {x}_{1}\hfill & \hfill \Delta {x}_{2}\hfill \end{array}\right)\left(\begin{array}{cc}\hfill {g}_{11}\hfill & \hfill {g}_{12}\hfill \\ \hfill {g}_{21}\hfill & \hfill {g}_{22}\hfill \end{array}\right)\left(\begin{array}{c}\hfill \Delta {x}_{1}\hfill \\ \hfill \Delta {x}_{2}\hfill \end{array}\right)=1,$
with angle *θ* and semi-axes *a* and *b* given by (16)$tan\left(2\theta \right)=\frac{2{g}_{12}}{{g}_{11}-{g}_{22}},$
(17)$a=\frac{1}{\sqrt{{g}_{22}+{g}_{12}cot\theta}},$
(18)$b=\frac{1}{\sqrt{{g}_{11}-{g}_{12}cot\theta}}.$

## System Architecture

Since the computation of generic colour space transforms and, in partiular the composition of their Jacobian matrices can be a tedious and error-prone process (see, e.g., Pant & Farup, 2012), an object-oriented framework for transforming colour and metric data between colour spaces has been implemented as a Python package colour. The package consists of six partially interdependent modules space, data, metric, tensor, statistics and misc. The relationship between the modules is shown in Fig. 1. In the figure, the arrows indicate dependencies between the modules in the form of Python imports. Each of the modules contain functions, classes and predefined objects with the purpose of simplifying the implementation of new colour spaces and metrics.

### Representing colour spaces

The core functionality of the colour space and colour metric transforms is found in the space module. The basic idea in designing the object oriented framework is to realise a colour space as an object, and to facilitate the construction of new such objects by providing classes for transforming new colour spaces from already existing ones. The class hierarchy which constistutes the core of the of the module, is shown in Fig. 2. All boxes represent classes, and the arrows denote class inheritance. Italicized method names indicate methods that should be overridden in a subclass. Details about attributes and auxiliary methods etc. have been left out for readability.

All colour space objects must derive from the abstract Space class, and as such implement the methods to_XYZ and from_XYZ for converting colour data between the XYZ colour space and the colour space represented by the object, and the methods jacobian_XYZ and inv_jacobian_XYZ for computing the corresponding Jacobian matrix and its inverse. The two latter methods are implemented in Space as inverses of each other, so the subclasses only need to implement one of them—the other one can be inherited. The colour data is represented as *N* × 3 NumPy (Oliphant, 2007) ndarray s, and the Jacobian matrices as *N* × 3 × 3 ndarray s.

All transformations between colour spaces must go through XYZ, which thus serves a special role, and has a separate class of its own. Here, the transformations are simply the identity transform, and the Jacobian matrices are identity matrices. All other colour spaces will be built by transforming colour data from an already existing colour space, starting from XYZ. To facilitate making the specific transforms, an abstract class Transform is provided. During instantiation of a transformed colour space object, the base space of the transformation has to be set. The virtual methods to_base and from_base for converting colour data to and from the base base, and jacobian_base and inv_jacobian_base for computing the Jacobian matrix and its inverse between the current space and the base space must be provided in the derived classes. The methods to_XYZ, from_XYZ, jacobian_XYZ, and inv_jacobian_XYZ are implemented in the base class Transform using the transformation between the current space and the base space (provided by derived classes) and the corresponding transformations in the base class, see Eq. (12). Hence, there is no need to reimplement these in the derived classes. Finally, the concrete colour space tranforms are implemented as classes TransformXXX derived from Transform. They must all implement the methods to_base, from_base and either jacobian_base or inv_jacobian_base. The remaining methods will be inferred by inheritance. In some cases, though, it is more efficient to provide more methods in order to reduce the computational cost. For example, in TransformLinear, both methods jacobian_base and inv_jacobian_base are provided in order to avoid inverting every single Jacobian matrix for large data sets.

### Representing colour and metric data

The colour space objects constructed by the method described above, will convert colour data represented as *N* × 3 ndarray s (*N* colour data points). For real-life applications, colours some times come as single data points (3-vectors), some times as lists of colour data (*N* × 3 matrices), and some times as images (*M* × *N* × 3 arrays). In order for the user not having to deal with converting back and forth between these formats, as well as remembering in which colour space all the data is given, a separate class Data for storing colour data has been implemented as part of the data module, cf. Fig. 3. Again, the boxes denote classes. In this module, there are no inheritance relationship between the classes, but they are related by the TensorData class having an attribute of the Data type.

A colour Data object can be instantiated with single colour data, lists of colour data, or colour images in any implemented colour space. The Data object takes the colour space (object) of the data as an argument, and keeps a dictionary of the colour spaces in which the data in question has been computed. When the colour data in a given space is requested (using the get method), it first checks the dictionary whether it has already been computed. If not, it is computed, stored in the dictionary and returned. All the actual computations are taken care of by the hierarcy of colour space objects representing the transforms necessary for building the colour space.

A similar approach is taken for colour metric data in the form of colour tensors, see Fig. 3. In this case, both the locations of the colour metrics (as colour Data), and the metrics themselves are represented in the class. Like for the colour data, a dictionary of the computed tensors is maintained. For the conversion between the different colour spaces, the Jacobian matrices are applied according to Eq. (10). The nested tensor transforms, Eq. (12), are implicitly taken care of by the colour space class hierarchy without the user having to interfere.

### Colour metrics, tensors and statistics

The four remaining modules, metric, tensor, statistics, and misc contain separate functions (not part of the class hierarchy) for computing various properties of colour data, colour transforms, and sets of these. The metric module has functions for computing the most common colour metrics, such as the standard CIE Δ*E*_{ab} and Δ*E*_{uv} metrics, CIEDE2000 (Luo, Cui & Rigg, 2001), the different versions of the DIN99 metric as described by Cui et al. (2002), the log-compressed OSA-UCS metric Δ*E*_{E} by Oleari, Melgosa & Huertas (2009), as well as a general Euclidean distance and the Poincare disk metric developed in Reference (Farup, 2014) in any colour space. All these functions take two colour data objects of the same size as arguments, and return an *N*-vector of colour differences.

The tensor module has functions for computing the metric tensors corresponding to the metrics in the metric package. The functions take one colour Data object as argument, and returns the corresponding TensorData object. The statistics module contains functions for calculating various statistics of colour metric data, such as the STRESS measure (García et al., 2007) and Pant’s R-values (Pant & Farup, 2012; Pant, Farup & Melgosa, 2013). The misc module contains miscellaneous supporting functions.

### Computational complexity

The framework has been implemented using NumPy (Oliphant, 2007) ndarray s, and all operations for colour and metric conversion are vectorised. Thus, all the real computations take place using the highly optimised underlying libraries for matrices, such as LAPACK (Anderson et al., 1999) etc. No loops over individual colour data are implemented in the high-level language. Thus, there are only two sources of computational overhead by using the framework.

First, there are the function calls associated with computing the transformations between a given colour space and its bases all the way back to the CIEXYZ colour space. These function calls will only take place once per transformation call. This can be a significant overhead when the data set consists of only one or very few data points, but then the computation is very quick anyway. For bigger colour data sets, such as images, this will represent only very few function calls (given by the number of steps in the transformation from CIEXYZ to the given space), and thus be negligible in comparison with the real computation, which takes place at the highly optimised low-level code.

Secondly, all colour and metric conversions go through the CIEXYZ colour space. When converting between two colour spaces based on a common basis *other than* CIEXYZ, an unneccesary conversion back and forth between this common basis and CIEXYZ will inevitably take place. It would, in principle, be possible to eliminate this by advanced optimisation techniques, but since the computations are already fast (fractions of a second even for quite large images), and the goal of the framework has been ease of implementation rather than computational efficiency, this has not been prioritized.

Not all of the operations in the statistics module are vectorised, although this would in principle also be possible. The reason for this is that they are mainly meant for colour research applications, and as such, they are not expected to be used in production. For the relevant use in research, data collection etc. will be much more time consuming than the actual computations, so computational efficiency has not been emphasized in this part of the framework.

## Example Application

In order to demonstrate the power of the proposed approach, a simple demo application is shown in Fig. 4. In this short code (less than a page), (i) a new colour space is implemented, (ii) individual colours, lists of colours and a colour image is converted to the new colour space, (iii) the tensorial data corresponding to the MacAdam ellipses is converted by the help of the Jacobian matrices of the transformation to the new space, and (iv) the colour difference between two images is computed as a Eucildean distance in the newly constructed colour space. These operations would normally require days of programming, but with the use of the proposed framework it is all achieved by a few lines of code.

In lines 3–6, the library is imported. In a real application, one would normally only import the colour package (import colour), and refer to the elements as, e.g., colour.space.TransformLinear etc., but here the specific classes, objects and functions needed are imported specifically, simply in order to reduce the size and improve the readability of the remaining code.

The transformation to the IPT colour space (Ebner & Fairchild, 1998) is composed of a linear transform from XYZ followed by a gamma correction, followed by final linear transform. The code for constructing this colour space is given in lines 9–16 of Fig. 4. It should be noted that the programmer does not need to specify anything about the computation of the corresponding Jacobian matrices—everyting is taken care of by the constructors of the Transformation classes.

Once the colour space is constructed, the Data class can use it for converting colours in various formats such as single data points—lines 19–20—giving

lists of colour points—lines 22–23—giving[9.99987871e-01 1.16264986e-03 1.69020684e-06],

and even colour images (lines 25–26). The individual IPT colour planes of the image shown in Fig. 5A resulting from this (lines 27–29) are shown in Fig. 6.[[9.99987871e-01 1.16264986e-03 1.69020684e-06]

[4.83120653e-01 5.61706973e-04 8.16583738e-07]

[0.00000000e+00 0.00000000e+00 0.00000000e+00]],

In order to demonstrate also the transformation of tensorial colour data, the code in lines 32–37 loads, transforms, and plots the MacAdam ellipses (MacAdam, 1942) in the PT-plane of the IPT space. The latter includes the tedious process of computing the transformation of the corresponding metric tensors according to Eq. (10). The resulting plot is shown in Fig. 7.

Similarly, the colour.metric module can compute colour differences of colour data in any format, including images. For example, the code in lines 41–43 of Fig. 4 computes the difference maps of the two images shown in Fig. 5 for Δ*E*_{ab}, Δ*E*_{00} and the Euclidean distance in the newly implemented IPT colour space. The results are shown in Fig. 8.

Please note that the *entire* code used to generate Figs. 5–8 is shown in Fig. 4.

## Conclusion

An object-oriented computational framework for colour metrics and colour has been designed and implemented in Python. The framework strongly simplifies the implementation of new colour spaces for transfroming colour data and as well as tensorial colour metric data between the various colour spaces without compromising too much on the computational complexity. The code is freely available at GitHub (https://github.com/ifarup/colourspace). Future extensions could include ICC support, computation geodesics based on the colour metrics (Pant & Farup, 2013), computation and representation of colour gamuts (Bakke, Farup & Hardeberg, 2010), as well as gamut mapping algorithms (Alsam & Farup, 2009) in any colour space, and under any colour metric.