A Plea for Colour Analysis Tools in DCC Applications


As we are increasingly using wide gamut colourspaces (ACES RGB, Rec. 2020, ProPhoto RGB) in the VFX industry, the need for better colour analysis tools is more important than ever. Today, nothing prevents an artist to generate or pick colours outside a given volume.

We think that people working with DCC applications from Autodesk, Adobe and The Foundry need better tools to work with colour. This is especially true when we tend to work in scene referred lighting with floating point values.

We are using the ACES RGB Sony F35 still life image from this directory for our manipulation:


More images are available there: https://www.dropbox.com/sh/bwfrqgyt20gz4dp/XShJffwvXR

This still life image exhibits a lot of colours that are very hard to reproduce and is a great example for the purpose of this document.

From ACES RGB Colourspace...

We first read the image using OpenimageIO and display it directly onto our sRGB display device. We apply the sRGB colourspace opto-electronic conversion function and ensure that values are in domain [0, 1] so that Matplotlib can display the image.

In [1]:
import numpy as np
import pylab
from OpenImageIO import FLOAT, ImageInput

import colour
from colour.plotting import *
from colour.utilities import message_box

def exr_image_as_array(path):
    image = ImageInput.open(path)
    specification = image.spec()

    return np.array(image.read_image(FLOAT)).reshape((specification.height,

def image_plot(image,
    vectorised_oecf = np.vectorize(OECF)
    image = np.clip(vectorised_oecf(image), 0, 1)

    settings = {'no_ticks': True,
                'bounding_box': [0, 1, 0, 1],
                'bbox_inches': 'tight',
                'pad_inches': 0}


ACES_image = exr_image_as_array('resources/images/SonyF35.StillLife_medium.exr')

... to sRGB Colourspace

We will convert the existing data to sRGB colourspace, in order to do so, we first compute the transformation matrix.

Note: We are using CAT02 chromatic adaptation transform (CAT).

In [2]:
sRGB_w = colour.sRGB_COLOURSPACE.whitepoint
sRGB_p = colour.sRGB_COLOURSPACE.primaries
sRGB_XYZ_to_RGB = colour.sRGB_COLOURSPACE.XYZ_to_RGB_matrix
sRGB_RGB_to_XYZ = colour.sRGB_COLOURSPACE.RGB_to_XYZ_matrix

ACES_w = colour.ACES_RGB_COLOURSPACE.whitepoint
ACES_p = colour.ACES_RGB_COLOURSPACE.primaries

message_box('Computing "ACES RGB" colourspace to "sRGB" colourspace matrix.')

cat = colour.chromatic_adaptation_matrix(colour.xy_to_XYZ(ACES_w),
ACES_RGB_to_sRGB_matrix = np.dot(sRGB_XYZ_to_RGB,
                                 np.dot(cat, ACES_RGB_to_XYZ))

*                                                                             *
*   Computing "ACES RGB" colourspace to "sRGB" colourspace matrix.            *
*                                                                             *
[[ 2.52197696 -1.13706612 -0.38491081]
 [-0.27547671  1.36982658 -0.09434988]
 [-0.01598681 -0.14781833  1.16380514]]

We finally apply the transformation matrix to the image and display it.

In [3]:
ACES_image_shape = ACES_image.shape
sRGB_image = np.array([np.dot(ACES_RGB_to_sRGB_matrix, RGB) for RGB in ACES_image.reshape((-1, 3))])
sRGB_image = sRGB_image.reshape(ACES_image_shape)