colour-science.org - September 03, 2016
The purpose of this document is to illustrate the Academy Color Encoding System (ACES) CTL implementation internals and get a better understanding of the various transformations and state changes an image is going through while being processed with the aces-dev CTL codebase.
The existing codebase is sparse when it comes to documentation and explanation on the choice of a lot of constants and parameters, this document attempts to shed some light on some of the system components.
The aces-dev CTL implementation used is the following: https://github.com/hpd/aces-dev/commit/3579d014e60ff49426fc7e968cac68964f46dcfd
The document currently explores the following aspects and modules:
The CTL implementation is tested by processing two linear ramp images using ctlrender with the related CTL modules (or on the fly generated ones).
The ramps have respectively [0, 1] and [1, 65504] domains and 16384 samples each.
The processed output ramp images are loaded and plotted. If both output ramp images need to be plotted at same time (e.g. the RRT figure), they are concatenated together in a linear interpolator and log spaced samples are used to generate the figure so that the concatenated ramp low end coverage is increased.
Usage of a Sprague (1880) fifth-order polynomial interpolator in place of the linear interpolator does not significantly change the results.
import numpy as np
import os
import plotly.plotly as py
import pylab
import subprocess
from collections import OrderedDict
import colour
//anaconda/envs/colour-2.7/lib/python2.7/site-packages/matplotlib/font_manager.py:273: UserWarning: Matplotlib is building the font cache using fc-list. This may take a moment.
IO_DIRECTORY = os.path.join(os.getcwd(), 'resources', 'others', 'aces_ctl_analysis')
not os.path.exists(IO_DIRECTORY) and os.makedirs(IO_DIRECTORY)
CTL_ROOT_DIRECTORY = os.path.abspath(
os.path.join(os.getcwd(), '..', 'aces-dev', 'transforms', 'ctl'))
os.environ['CTL_MODULE_PATH'] = os.path.join(CTL_ROOT_DIRECTORY, 'lib')
CTL_RENDER = 'ctlrender'
CTL_DEFAULT_ARGUMENTS = ['-verbose', '-force']
def ctl_render(image_i, image_o, ctls, ctl_args=CTL_DEFAULT_ARGUMENTS):
ctls_arguments = []
for ctl in ctls:
ctls_arguments.append('-ctl')
ctls_arguments.append(ctl)
output = subprocess.check_output(
[CTL_RENDER] + ctl_args + [image_i, image_o] + ctls_arguments)
return output
MINIMUM_REPR_NUMBER = 5.96e-08
MAXIMUM_REPR_NUMBER = 65504
DEFAULT_SAMPLE_COUNT = 16384
SAMPLES_LDR = np.linspace(0, 1, DEFAULT_SAMPLE_COUNT)
SAMPLES_HDR = np.linspace(1, MAXIMUM_REPR_NUMBER, DEFAULT_SAMPLE_COUNT)
SAMPLES_LOG = np.logspace(np.log10(MINIMUM_REPR_NUMBER),
np.log10(MAXIMUM_REPR_NUMBER),
DEFAULT_SAMPLE_COUNT)
PLOTLY_FOLDER = '/colour-science/aces_ctl'
def write_linear_ramp_image(path, in_=0, out=1, samples=DEFAULT_SAMPLE_COUNT):
a = np.linspace(in_, out, samples)[np.newaxis, ...]
a = colour.tstack((a, a, a))
colour.write_image(a, path)
def concatenated_interpolator(
a,
b,
samples_a=SAMPLES_LDR,
samples_b=SAMPLES_HDR,
interpolator=colour.LinearInterpolator):
x = np.hstack((samples_a, samples_b))
y = np.hstack((a, b))
return colour.Extrapolator(interpolator(x, y))
LINEAR_RAMP_LDR_PATH = os.path.join(IO_DIRECTORY, 'linear_ramp_ldr.exr')
LINEAR_RAMP_HDR_PATH = os.path.join(IO_DIRECTORY, 'linear_ramp_hdr.exr')
write_linear_ramp_image(LINEAR_RAMP_LDR_PATH, 0, 1)
write_linear_ramp_image(LINEAR_RAMP_HDR_PATH, 1, MAXIMUM_REPR_NUMBER)
RRT_LDR_PATH = os.path.join(IO_DIRECTORY, 'RRT_ldr.exr')
RRT_HDR_PATH = os.path.join(IO_DIRECTORY, 'RRT_hdr.exr')
ctl_render(LINEAR_RAMP_LDR_PATH,
RRT_LDR_PATH,
(os.path.join(CTL_ROOT_DIRECTORY, 'rrt', 'RRT.ctl'), ))
ctl_render(LINEAR_RAMP_HDR_PATH,
RRT_HDR_PATH,
(os.path.join(CTL_ROOT_DIRECTORY, 'rrt', 'RRT.ctl'), ))
RRT_LDR_IMAGE = colour.read_image(RRT_LDR_PATH)
RRT_HDR_IMAGE = colour.read_image(RRT_HDR_PATH)
name = 'RRT'
figure = pylab.figure()
for i, axis in enumerate(('R', 'G', 'B')):
RRT_interpolator = concatenated_interpolator(
RRT_LDR_IMAGE[..., i], RRT_HDR_IMAGE[..., i])
pylab.loglog(SAMPLES_LOG,
RRT_interpolator(SAMPLES_LOG),
label=axis,
linewidth=2)
pylab.xlabel('Input - Log')
pylab.ylabel('Output - Log')
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))
//anaconda/envs/colour-2.7/lib/python2.7/site-packages/plotly/plotly/plotly.py:1443: UserWarning: Estimated Draw Time Slow
The draw time for this plot will be slow for clients without much RAM.
name = 'RRT - Gradient - [0, 1]'
figure = pylab.figure()
pylab.plot(SAMPLES_LDR,
np.gradient(RRT_LDR_IMAGE[..., 0]),
linewidth=2)
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))
ODT_RGB_MONITOR_LDR_PATH = os.path.join(IO_DIRECTORY, 'ODT_rgb_monitor_ldr.exr')
ctl_render(LINEAR_RAMP_LDR_PATH,
ODT_RGB_MONITOR_LDR_PATH,
(os.path.join(CTL_ROOT_DIRECTORY, 'odt', 'rgbMonitor', 'ODT.Academy.RGBmonitor_100nits_dim.ctl'), ))
name = 'ODT.Academy.RGBmonitor_100nits_dim'
figure = pylab.figure()
pylab.loglog(SAMPLES_LDR,
colour.read_image(ODT_RGB_MONITOR_LDR_PATH)[..., 0],
linewidth=2)
pylab.xlabel('Input - Log')
pylab.ylabel('Output - Log')
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))
name = 'ODT.Academy.RGBmonitor_100nits_dim - Gradient'
figure = pylab.figure()
pylab.plot(SAMPLES_LDR,
np.gradient(colour.read_image(ODT_RGB_MONITOR_LDR_PATH)[..., 0]),
linewidth=2)
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))
ODT_RGB_MONITOR_RRT_LDR_PATH = os.path.join(IO_DIRECTORY, 'ODT_rgb_monitor_RRT_ldr.exr')
ctl_render(LINEAR_RAMP_LDR_PATH,
ODT_RGB_MONITOR_RRT_LDR_PATH,
(os.path.join(CTL_ROOT_DIRECTORY, 'rrt', 'RRT.ctl'),
os.path.join(CTL_ROOT_DIRECTORY, 'odt', 'rgbMonitor', 'ODT.Academy.RGBmonitor_100nits_dim.ctl')))
name = 'ODT.Academy.RGBmonitor_100nits_dim(RRT)'
figure = pylab.figure()
pylab.loglog(SAMPLES_LDR,
colour.read_image(ODT_RGB_MONITOR_RRT_LDR_PATH)[..., 0],
linewidth=2)
pylab.xlabel('Input - Log')
pylab.ylabel('Output - Log')
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))
ODT_SEGMENTED_SPLINE_PARAMETERS_CTL = (
'ODT_48nits', 'ODT_1000nits', 'ODT_2000nits', 'ODT_4000nits')
SEGMENTED_SPLINE_C9_FWD_CTL = """
// Colour - CTL - segmented_spline_c9_fwd
import "ACESlib.Utilities";
import "ACESlib.Tonescales";
void main
(
input varying float rIn,
input varying float gIn,
input varying float bIn,
input varying float aIn,
output varying float rOut,
output varying float gOut,
output varying float bOut,
output varying float aOut
)
{{
rOut = segmented_spline_c9_fwd(rIn, {0});
gOut = segmented_spline_c9_fwd(gIn, {0});
bOut = segmented_spline_c9_fwd(bIn, {0});
aOut = aIn;
}}
"""[1:]
SEGMENTED_SPLINE_C9_FWD_INTERPOLATORS = OrderedDict()
for parameter in ODT_SEGMENTED_SPLINE_PARAMETERS_CTL:
segmented_spline_c9_fwd_ctl_path = os.path.join(
IO_DIRECTORY, 'segmented_spline_c9_fwd_{0}.ctl'.format(
parameter))
with open(segmented_spline_c9_fwd_ctl_path, 'w') as file_:
file_.write(SEGMENTED_SPLINE_C9_FWD_CTL.format(parameter))
segmented_spline_c9_fwd_ldr_image_path = os.path.join(
IO_DIRECTORY, 'segmented_spline_c9_fwd_{0}_ldr.exr'.format(
parameter))
segmented_spline_c9_fwd_hdr_image_path = os.path.join(
IO_DIRECTORY, 'segmented_spline_c9_fwd_{0}_hdr.exr'.format(
parameter))
ctl_render(LINEAR_RAMP_LDR_PATH,
segmented_spline_c9_fwd_ldr_image_path,
(segmented_spline_c9_fwd_ctl_path, ))
ctl_render(LINEAR_RAMP_HDR_PATH,
segmented_spline_c9_fwd_hdr_image_path,
(segmented_spline_c9_fwd_ctl_path, ))
SEGMENTED_SPLINE_C9_FWD_INTERPOLATORS[parameter] = (
concatenated_interpolator(
colour.read_image(segmented_spline_c9_fwd_ldr_image_path)[..., 0],
colour.read_image(segmented_spline_c9_fwd_hdr_image_path)[..., 0]))
name = 'ACESlib.Tonescales.segmented_spline_c9_fwd - [{0}, {1}]'.format(
MINIMUM_REPR_NUMBER, MAXIMUM_REPR_NUMBER)
figure = pylab.figure()
for name_i, interpolator in SEGMENTED_SPLINE_C9_FWD_INTERPOLATORS.items():
pylab.loglog(SAMPLES_LOG,
interpolator(SAMPLES_LOG),
label=name_i,
linewidth=2)
pylab.xlabel('Input - Log')
pylab.ylabel('Output - Log')
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))
The draw time for this plot will be slow for clients without much RAM.
SAMPLES_ZOOM = np.linspace(0, 0.18, DEFAULT_SAMPLE_COUNT)
name = 'ACESlib.Tonescales.segmented_spline_c9_fwd - ODT_48nits'
figure = pylab.figure()
pylab.loglog(SAMPLES_ZOOM,
SEGMENTED_SPLINE_C9_FWD_INTERPOLATORS['ODT_48nits'](SAMPLES_ZOOM),
label=name_i,
linewidth=2)
pylab.xlabel('Input - Log')
pylab.ylabel('Output - Log')
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))
name = 'ACESlib.Tonescales.segmented_spline_c9_fwd - ODT_48nits - Gradient'
figure = pylab.figure()
pylab.plot(SAMPLES_ZOOM,
np.gradient(SEGMENTED_SPLINE_C9_FWD_INTERPOLATORS['ODT_48nits'](SAMPLES_ZOOM)),
label=name_i,
linewidth=2)
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))
name = 'ACESlib.Tonescales.segmented_spline_c9_fwd - ODT_1000nits'
figure = pylab.figure()
pylab.loglog(SAMPLES_ZOOM,
SEGMENTED_SPLINE_C9_FWD_INTERPOLATORS['ODT_1000nits'](SAMPLES_ZOOM),
label=name_i,
linewidth=2)
pylab.xlabel('Input - Log')
pylab.ylabel('Output - Log')
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))
name = 'ACESlib.Tonescales.segmented_spline_c9_fwd - ODT_1000nits - Gradient'
figure = pylab.figure()
pylab.plot(SAMPLES_ZOOM,
np.gradient(SEGMENTED_SPLINE_C9_FWD_INTERPOLATORS['ODT_1000nits'](SAMPLES_ZOOM)),
label=name_i,
linewidth=2)
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))
name = 'ACESlib.Tonescales.segmented_spline_c9_fwd - ODT_2000nits'
figure = pylab.figure()
pylab.loglog(SAMPLES_ZOOM,
SEGMENTED_SPLINE_C9_FWD_INTERPOLATORS['ODT_2000nits'](SAMPLES_ZOOM),
label=name_i,
linewidth=2)
pylab.xlabel('Input - Log')
pylab.ylabel('Output - Log')
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))
name = 'ACESlib.Tonescales.segmented_spline_c9_fwd - ODT_2000nits - Gradient'
figure = pylab.figure()
pylab.plot(SAMPLES_ZOOM,
np.gradient(SEGMENTED_SPLINE_C9_FWD_INTERPOLATORS['ODT_2000nits'](SAMPLES_ZOOM)),
label=name_i,
linewidth=2)
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))
name = 'ACESlib.Tonescales.segmented_spline_c9_fwd - ODT_4000nits'
figure = pylab.figure()
pylab.loglog(SAMPLES_ZOOM,
SEGMENTED_SPLINE_C9_FWD_INTERPOLATORS['ODT_4000nits'](SAMPLES_ZOOM),
label=name_i,
linewidth=2)
pylab.xlabel('Input - Log')
pylab.ylabel('Output - Log')
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))
name = 'ACESlib.Tonescales.segmented_spline_c9_fwd - ODT_4000nits - Gradient'
figure = pylab.figure()
pylab.plot(SAMPLES_ZOOM,
np.gradient(SEGMENTED_SPLINE_C9_FWD_INTERPOLATORS['ODT_4000nits'](SAMPLES_ZOOM)),
label=name_i,
linewidth=2)
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))
DARKSURROUND_TO_DIMSURROUND_CTL = """
// Colour - CTL - darkSurround_to_dimSurround
import "ACESlib.Utilities";
import "ACESlib.Transform_Common";
import "ACESlib.ODT_Common";
void main
(
input varying float rIn,
input varying float gIn,
input varying float bIn,
input varying float aIn,
output varying float rOut,
output varying float gOut,
output varying float bOut,
output varying float aOut
)
{{
float rgb[3];
rgb[0] = rIn;
rgb[1] = gIn;
rgb[2] = bIn;
rgb = darkSurround_to_dimSurround(rgb);
rOut = rgb[0];
gOut = rgb[1];
bOut = rgb[2];
aOut = aIn;
}}
"""[1:]
DARKSURROUND_TO_DIMSURROUND_CTL_PATH = os.path.join(
IO_DIRECTORY, 'darkSurround_to_dimSurround.ctl')
with open(DARKSURROUND_TO_DIMSURROUND_CTL_PATH, 'w') as file_:
file_.write(DARKSURROUND_TO_DIMSURROUND_CTL)
DARKSURROUND_TO_DIMSURROUND_LDR_PATH = os.path.join(IO_DIRECTORY, 'darksurround_to_dimsurround_ldr.exr')
ctl_render(LINEAR_RAMP_LDR_PATH,
DARKSURROUND_TO_DIMSURROUND_LDR_PATH,
(DARKSURROUND_TO_DIMSURROUND_CTL_PATH, ))
name = 'ACESlib.ODT_Common.darkSurround_to_dimSurround'
figure = pylab.figure()
pylab.plot(SAMPLES_LDR,
colour.read_image(DARKSURROUND_TO_DIMSURROUND_LDR_PATH)[..., 0],
label='darkSurround_to_dimSurround',
linewidth=2)
pylab.plot(SAMPLES_LDR, SAMPLES_LDR, label='f(x)=x', linewidth=2)
pylab.title(name)
py.iplot_mpl(figure, filename='/'.join((PLOTLY_FOLDER, name)))