# -*- coding: utf-8 -*-
"""
Adobe DNG SDK Colour Processing
===============================
Defines various objects implementing *Adobe DNG SDK* colour processing:
- :func:`colour_hdri.xy_to_camera_neutral`
- :func:`colour_hdri.camera_neutral_to_xy`
- :func:`colour_hdri.XYZ_to_camera_space_matrix`
- :func:`colour_hdri.camera_space_to_XYZ_matrix`
The *Adobe DNG SDK* defines the following tags relevant for the current
implementation:
- *CalibrationIlluminant1* : The illuminant used for the first set of
colour calibration tags.
- *CalibrationIlluminant2* : The illuminant used for an optional second set
of colour calibration tags.
- *ColorMatrix1* : *ColorMatrix1* defines a transformation matrix that
converts XYZ values to reference camera native colour space values, under
the first calibration illuminant.
- *ColorMatrix2* : *ColorMatrix2* defines a transformation matrix that
converts XYZ values to reference camera native colour space values, under
the second calibration illuminant.
- *CameraCalibration1* : *CameraCalibration1* defines a calibration matrix
that transforms reference camera native space values to individual camera
native space values under the first calibration illuminant.
This matrix is stored separately from the matrix specified by the
*ColorMatrix1* tag to allow raw converters to swap in replacement colour
matrices based on *UniqueCameraModel* tag, while still taking advantage of
any per-individual camera calibration performed by the camera manufacturer.
- *CameraCalibration2* : *CameraCalibration2* defines a calibration matrix
that transforms reference camera native space values to individual camera
native space values under the second calibration illuminant.
This matrix is stored separately from the matrix specified by the
*ColorMatrix2* tag to allow raw converters to swap in replacement colour
matrices based on *UniqueCameraModel* tag, while still taking advantage of
any per-individual camera calibration performed by the camera manufacturer.
- *ReductionMatrix1* : *ReductionMatrix1* defines a dimensionality reduction
matrix for use as the first stage in converting colour camera native space
values to XYZ values, under the first calibration illuminant. This tag may
only be used if *ColorPlanes* is greater than 3.
- *ReductionMatrix2* : *ReductionMatrix2* defines a dimensionality reduction
matrix for use as the first stage in converting colour camera native space
values to XYZ values, under the second calibration illuminant. This tag
may only be used if *ColorPlanes* is greater than 3.
- *AnalogBalance* : Normally the stored raw values are not white balanced,
since any digital white balancing will reduce the dynamic range of the
final image if the user decides to later adjust the white balance;
however, if camera hardware is capable of white balancing the colour
channels before the signal is digitized, it can improve the dynamic range
of the final image.
*AnalogBalance* defines the gain, either analog (recommended) or digital
(not recommended) that has been applied the stored raw values.
- *AsShotNeutral* : *AsShotNeutral* specifies the selected white balance at
time of capture, encoded as the coordinates of a perfectly neutral colour
in linear reference space values. The inclusion of this tag precludes the
inclusion of the *AsShotWhiteXY* tag.
- *AsShotWhiteXY* : *AsShotWhiteXY* specifies the selected white balance at
time of capture, encoded as x-y chromaticity coordinates. The inclusion of
this tag precludes the inclusion of the *AsShotNeutral* tag.
- *ForwardMatrix1* : This tag defines a matrix that maps white balanced
camera colours to XYZ D50 colours.
- *ForwardMatrix2* : This tag defines a matrix that maps white balanced
camera colours to XYZ D50 colours.
Notes
-----
- At least one of the *ColorMatrix1* or *ColorMatrix2* tags must be included
in the camera profile, the current implementation expects them to be passed
as identity matrices if not included.
- If the *ForwardMatrix1* or *ForwardMatrix2* tags are not included in the
camera profile, the current implementation expects them to be passed as
identity matrices.
- The *ReductionMatrix1* and *ReductionMatrix2* tags are ignored by the
current implementation which expects cameras with 3 colour planes.
- *DNG 1.2.0.0* and later supports different companies creating the camera
calibration tags using different reference cameras.
When rendering a *DNG* file using a camera profile, it is important to
know if the selected camera profile was designed using the same reference
camera used to create the camera calibration tags. If so, then the camera
calibration tags should be used. If not, then it is preferable to ignore
the camera calibration tags and use identity matrices instead in order to
minimize the worse case calibration mismatch error.
This matching is done by comparing the *CameraCalibrationSignature* tag
and the *ProfileCalibrationSignature* tag for the selected camera profile.
If they match, then use the camera calibration tags. If not, then use
identity matrices.
- The Hue/Saturation/Value Mapping Table is ignored by the current
implementation because deemed unsuitable :cite:`McGuffog2012a`.
- The various matrices used in this module are extracted from a
*Canon EOS 5D Mark II* camera.
References
----------
- :cite:`AdobeSystems2012d` : Adobe Systems. (2012). Translating White
Balance xy Coordinates to Camera Neutral Coordinates. In Digital Negative
(DNG) Specification (p. 80).
- :cite:`AdobeSystems2012d` : Adobe Systems. (2012). Translating Camera
Neutral Coordinates to White Balance xy Coordinates. In Digital Negative
(DNG) Specification (pp. 80-81).
- :cite:`AdobeSystems2012f` : Adobe Systems. (2012). Digital Negative (DNG)
Specification.
- :cite:`AdobeSystems2012g` : Adobe Systems. (2012). Camera to XYZ (D50)
Transform. In Digital Negative (DNG) Specification (p. 81).
- :cite:`AdobeSystems2015d` : Adobe Systems. (2015). Adobe DNG SDK 1.4.
Retrieved from http://download.adobe.com/pub/adobe/dng/dng_sdk_1_4.zip
- :cite:`McGuffog2012a` : McGuffog, S. (2012). Hue Twists in DNG Camera
Profiles. Retrieved October 29, 2016, from
http://dcptool.sourceforge.net/Hue Twists.html
"""
from __future__ import division, unicode_literals
import numpy as np
from colour.adaptation import chromatic_adaptation_matrix_VonKries
from colour.algebra import is_identity
from colour.constants import EPSILON
from colour.models import UCS_to_uv, XYZ_to_UCS, XYZ_to_xy, xy_to_XYZ
from colour.utilities import (dot_matrix, dot_vector, linear_conversion,
tstack)
from colour.temperature import uv_to_CCT_Robertson1968
from colour_hdri.models import ADOBE_DNG_XYZ_ILLUMINANT
__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2015-2019 - Colour Developers'
__license__ = 'New BSD License - http://opensource.org/licenses/BSD-3-Clause'
__maintainer__ = 'Colour Developers'
__email__ = 'colour-science@googlegroups.com'
__status__ = 'Production'
__all__ = [
'interpolated_matrix', 'xy_to_camera_neutral', 'camera_neutral_to_xy',
'XYZ_to_camera_space_matrix', 'camera_space_to_XYZ_matrix'
]
def interpolated_matrix(CCT, CCT_1, CCT_2, M_1, M_2):
"""
Computes the matrix interpolated from :math:`CCT_1` and :math:`CCT_2`
correlated colour temperatures to respectively :math:`M_T` and :math:`M_R`
colour matrices using given correlated colour temperature :math:`CCT`
interpolation value.
Parameters
----------
CCT : numeric
Correlated colour temperature :math:`CCT`.
CCT_1 : numeric
Correlated colour temperature :math:`CCT_1`.
CCT_2 : numeric
Correlated colour temperature :math:`CCT_2`.
M_1 : array_like
:math:`M_T` colour matrix.
M_2 : array_like
:math:`M_R` colour matrix.
Returns
-------
ndarray
Interpolated colour matrix :math:`M_i`.
Notes
-----
- The computation is performed in mired (MIcro REciprocal Degree,
reciprocal megakelvin) :math:`MK^{-1}`.
Examples
--------
>>> CCT = 5000
>>> CCT_1 = 2850
>>> CCT_2 = 6500
>>> M_T = np.array([
... [0.5309, -0.0229, -0.0336],
... [-0.6241, 1.3265, 0.3337],
... [-0.0817, 0.1215, 0.6664]])
>>> M_R = np.array([
... [0.4716, 0.0603, -0.0830],
... [-0.7798, 1.5474, 0.2480],
... [-0.1496, 0.1937, 0.6651]])
>>> interpolated_matrix(CCT, CCT_1, CCT_2, M_T, M_R) # doctest: +ELLIPSIS
array([[ 0.4854908..., 0.0408106..., -0.0714282...],
[-0.7433278..., 1.4956549..., 0.2680749...],
[-0.1336946..., 0.1767874..., 0.6654045...]])
"""
if CCT <= CCT_1:
return M_1
elif CCT >= CCT_2:
return M_2
else:
return linear_conversion(1e6 / CCT, (1e6 / CCT_1, 1e6 / CCT_2),
tstack([M_1, M_2]))
[docs]def xy_to_camera_neutral(xy, CCT_calibration_illuminant_1,
CCT_calibration_illuminant_2, M_color_matrix_1,
M_color_matrix_2, M_camera_calibration_1,
M_camera_calibration_2, analog_balance):
"""
Converts given *xy* white balance chromaticity coordinates to
*Camera Neutral* coordinates.
Parameters
----------
xy : array_like
*xy* white balance chromaticity coordinates.
CCT_calibration_illuminant_1 : numeric
Correlated colour temperature of *CalibrationIlluminant1*.
CCT_calibration_illuminant_2 : numeric
Correlated colour temperature of *CalibrationIlluminant2*.
M_color_matrix_1 : array_like
*ColorMatrix1* tag matrix.
M_color_matrix_2 : array_like
*ColorMatrix2* tag matrix.
M_camera_calibration_1 : array_like
*CameraCalibration1* tag matrix.
M_camera_calibration_2 : array_like
*CameraCalibration2* tag matrix.
analog_balance : array_like
*AnalogBalance* tag vector.
Returns
-------
ndarray
*Camera Neutral* coordinates.
References
----------
:cite:`AdobeSystems2012d`, :cite:`AdobeSystems2012f`,
:cite:`AdobeSystems2015d`, :cite:`McGuffog2012a`
Examples
--------
>>> M_color_matrix_1 = np.array(
... [[0.5309, -0.0229, -0.0336],
... [-0.6241, 1.3265, 0.3337],
... [-0.0817, 0.1215, 0.6664]])
>>> M_color_matrix_2 = np.array(
... [[0.4716, 0.0603, -0.0830],
... [-0.7798, 1.5474, 0.2480],
... [-0.1496, 0.1937, 0.6651]])
>>> M_camera_calibration_1 = np.identity(3)
>>> M_camera_calibration_2 = np.identity(3)
>>> analog_balance = np.ones(3)
>>> xy_to_camera_neutral( # doctest: +ELLIPSIS
... np.array([0.32816244, 0.34698169]),
... 2850,
... 6500,
... M_color_matrix_1,
... M_color_matrix_2,
... M_camera_calibration_1,
... M_camera_calibration_2,
... analog_balance)
array([ 0.4130699..., 1... , 0.646465...])
"""
M_XYZ_to_camera = XYZ_to_camera_space_matrix(
xy, CCT_calibration_illuminant_1, CCT_calibration_illuminant_2,
M_color_matrix_1, M_color_matrix_2, M_camera_calibration_1,
M_camera_calibration_2, analog_balance)
camera_neutral = dot_vector(M_XYZ_to_camera, xy_to_XYZ(xy))
camera_neutral /= camera_neutral[1]
return camera_neutral
[docs]def camera_neutral_to_xy(camera_neutral,
CCT_calibration_illuminant_1,
CCT_calibration_illuminant_2,
M_color_matrix_1,
M_color_matrix_2,
M_camera_calibration_1,
M_camera_calibration_2,
analog_balance,
epsilon=EPSILON):
"""
Converts given *Camera Neutral* coordinates to *xy* white balance
chromaticity coordinates.
Parameters
----------
camera_neutral : array_like
*Camera Neutral* coordinates.
CCT_calibration_illuminant_1 : numeric
Correlated colour temperature of *CalibrationIlluminant1*.
CCT_calibration_illuminant_2 : numeric
Correlated colour temperature of *CalibrationIlluminant2*.
M_color_matrix_1 : array_like
*ColorMatrix1* tag matrix.
M_color_matrix_2 : array_like
*ColorMatrix2* tag matrix.
M_camera_calibration_1 : array_like
*CameraCalibration1* tag matrix.
M_camera_calibration_2 : array_like
*CameraCalibration2* tag matrix.
analog_balance : array_like
*AnalogBalance* tag vector.
epsilon : numeric, optional
Threshold value for computation convergence.
Returns
-------
ndarray
*xy* white balance chromaticity coordinates.
Raises
------
RuntimeError
If the given *Camera Neutral* coordinates did not converge to *xy*
white balance chromaticity coordinates.
References
----------
:cite:`AdobeSystems2012e`, :cite:`AdobeSystems2012f`,
:cite:`AdobeSystems2015d`, :cite:`McGuffog2012a`
Examples
--------
>>> M_color_matrix_1 = np.array(
... [[0.5309, -0.0229, -0.0336],
... [-0.6241, 1.3265, 0.3337],
... [-0.0817, 0.1215, 0.6664]])
>>> M_color_matrix_2 = np.array(
... [[0.4716, 0.0603, -0.0830],
... [-0.7798, 1.5474, 0.2480],
... [-0.1496, 0.1937, 0.6651]])
>>> M_camera_calibration_1 = np.identity(3)
>>> M_camera_calibration_2 = np.identity(3)
>>> analog_balance = np.ones(3)
>>> camera_neutral_to_xy( # doctest: +ELLIPSIS
... np.array([0.413070, 1.000000, 0.646465]),
... 2850,
... 6500,
... M_color_matrix_1,
... M_color_matrix_2,
... M_camera_calibration_1,
... M_camera_calibration_2,
... analog_balance)
array([ 0.3281624..., 0.3469816...])
"""
# Initial *xy* chromaticity coordinates guess.
xy = np.array((1 / 3, 1 / 3))
while True:
xy_p = np.copy(xy)
M_XYZ_to_camera = XYZ_to_camera_space_matrix(
xy, CCT_calibration_illuminant_1, CCT_calibration_illuminant_2,
M_color_matrix_1, M_color_matrix_2, M_camera_calibration_1,
M_camera_calibration_2, analog_balance)
XYZ = dot_vector(np.linalg.inv(M_XYZ_to_camera), camera_neutral)
xy = XYZ_to_xy(XYZ)
if np.abs(np.sum(xy_p - xy)) <= epsilon:
return xy
raise RuntimeError(
'"Camera Neutral" coordinates "{0}" did not converge to "xy" white '
'balance chromaticity coordinates!'.format(xy))
[docs]def XYZ_to_camera_space_matrix(xy, CCT_calibration_illuminant_1,
CCT_calibration_illuminant_2, M_color_matrix_1,
M_color_matrix_2, M_camera_calibration_1,
M_camera_calibration_2, analog_balance):
"""
Returns the *CIE XYZ* to *Camera Space* matrix for given *xy* white balance
chromaticity coordinates.
Parameters
----------
xy : array_like
*xy* white balance chromaticity coordinates.
CCT_calibration_illuminant_1 : numeric
Correlated colour temperature of *CalibrationIlluminant1*.
CCT_calibration_illuminant_2 : numeric
Correlated colour temperature of *CalibrationIlluminant2*.
M_color_matrix_1 : array_like
*ColorMatrix1* tag matrix.
M_color_matrix_2 : array_like
*ColorMatrix2* tag matrix.
M_camera_calibration_1 : array_like
*CameraCalibration1* tag matrix.
M_camera_calibration_2 : array_like
*CameraCalibration2* tag matrix.
analog_balance : array_like
*AnalogBalance* tag vector.
Returns
-------
ndarray
*CIE XYZ* to *Camera Space* matrix.
Notes
-----
- The reference illuminant is D50 as defined per
:attr:`colour_hdri.models.dataset.dng.ADOBE_DNG_XYZ_ILLUMINANT`
attribute.
References
----------
:cite:`AdobeSystems2012f`, :cite:`AdobeSystems2015d`, :cite:`McGuffog2012a`
Examples
--------
>>> M_color_matrix_1 = np.array(
... [[0.5309, -0.0229, -0.0336],
... [-0.6241, 1.3265, 0.3337],
... [-0.0817, 0.1215, 0.6664]])
>>> M_color_matrix_2 = np.array(
... [[0.4716, 0.0603, -0.0830],
... [-0.7798, 1.5474, 0.2480],
... [-0.1496, 0.1937, 0.6651]])
>>> M_camera_calibration_1 = np.identity(3)
>>> M_camera_calibration_2 = np.identity(3)
>>> analog_balance = np.ones(3)
>>> XYZ_to_camera_space_matrix( # doctest: +ELLIPSIS
... np.array([0.34510414, 0.35162252]),
... 2850,
... 6500,
... M_color_matrix_1,
... M_color_matrix_2,
... M_camera_calibration_1,
... M_camera_calibration_2,
... analog_balance)
array([[ 0.4854908..., 0.0408106..., -0.0714282...],
[-0.7433278..., 1.4956549..., 0.2680749...],
[-0.1336946..., 0.1767874..., 0.6654045...]])
"""
M_AB = np.diagflat(analog_balance)
uv = UCS_to_uv(XYZ_to_UCS(xy_to_XYZ(xy)))
CCT, _D_uv = uv_to_CCT_Robertson1968(uv)
if is_identity(M_color_matrix_1) or is_identity(M_color_matrix_2):
M_CM = (M_color_matrix_1
if is_identity(M_color_matrix_2) else M_color_matrix_2)
else:
M_CM = interpolated_matrix(CCT, CCT_calibration_illuminant_1,
CCT_calibration_illuminant_2,
M_color_matrix_1, M_color_matrix_2)
M_CC = interpolated_matrix(CCT, CCT_calibration_illuminant_1,
CCT_calibration_illuminant_2,
M_camera_calibration_1, M_camera_calibration_2)
M_XYZ_to_camera_space = dot_matrix(dot_matrix(M_AB, M_CC), M_CM)
return M_XYZ_to_camera_space
[docs]def camera_space_to_XYZ_matrix(xy,
CCT_calibration_illuminant_1,
CCT_calibration_illuminant_2,
M_color_matrix_1,
M_color_matrix_2,
M_camera_calibration_1,
M_camera_calibration_2,
analog_balance,
M_forward_matrix_1,
M_forward_matrix_2,
chromatic_adaptation_transform='Bradford'):
"""
Returns the *Camera Space* to *CIE XYZ* matrix for given *xy* white
balance chromaticity coordinates.
Parameters
----------
xy : array_like
*xy* white balance chromaticity coordinates.
CCT_calibration_illuminant_1 : numeric
Correlated colour temperature of *CalibrationIlluminant1*.
CCT_calibration_illuminant_2 : numeric
Correlated colour temperature of *CalibrationIlluminant2*.
M_color_matrix_1 : array_like
*ColorMatrix1* tag matrix.
M_color_matrix_2 : array_like
*ColorMatrix2* tag matrix.
M_camera_calibration_1 : array_like
*CameraCalibration1* tag matrix.
M_camera_calibration_2 : array_like
*CameraCalibration2* tag matrix.
analog_balance : array_like
*AnalogBalance* tag vector.
M_forward_matrix_1 : array_like
*ForwardMatrix1* tag matrix.
M_forward_matrix_2 : array_like
*ForwardMatrix2* tag matrix.
chromatic_adaptation_transform : unicode, optional
**{'CAT02', 'XYZ Scaling', 'Von Kries', 'Bradford', 'Sharp',
'Fairchild', 'CMCCAT97', 'CMCCAT2000', 'CAT02_BRILL_CAT', 'Bianco',
'Bianco PC'}**,
Chromatic adaptation transform.
Returns
-------
ndarray
*Camera Space* to *CIE XYZ* matrix.
Notes
-----
- The reference illuminant is D50 as defined per
:attr:`colour_hdri.models.dataset.dng.ADOBE_DNG_XYZ_ILLUMINANT`
attribute.
References
----------
:cite:`AdobeSystems2012f`, :cite:`AdobeSystems2012g`,
:cite:`AdobeSystems2015d`, :cite:`McGuffog2012a`
Examples
--------
>>> M_color_matrix_1 = np.array(
... [[0.5309, -0.0229, -0.0336],
... [-0.6241, 1.3265, 0.3337],
... [-0.0817, 0.1215, 0.6664]])
>>> M_color_matrix_2 = np.array(
... [[0.4716, 0.0603, -0.0830],
... [-0.7798, 1.5474, 0.2480],
... [-0.1496, 0.1937, 0.6651]])
>>> M_camera_calibration_1 = np.identity(3)
>>> M_camera_calibration_2 = np.identity(3)
>>> analog_balance = np.ones(3)
>>> M_forward_matrix_1 = np.array(
... [[0.8924, -0.1041, 0.1760],
... [0.4351, 0.6621, -0.0972],
... [0.0505, -0.1562, 0.9308]])
>>> M_forward_matrix_2 = np.array(
... [[0.8924, -0.1041, 0.1760],
... [0.4351, 0.6621, -0.0972],
... [0.0505, -0.1562, 0.9308]])
>>> camera_space_to_XYZ_matrix( # doctest: +ELLIPSIS
... np.array([0.32816244, 0.34698169]),
... 2850,
... 6500,
... M_color_matrix_1,
... M_color_matrix_2,
... M_camera_calibration_1,
... M_camera_calibration_2,
... analog_balance,
... M_forward_matrix_1,
... M_forward_matrix_2)
array([[ 2.1604087..., -0.1041... , 0.2722498...],
[ 1.0533324..., 0.6621... , -0.1503561...],
[ 0.1222553..., -0.1562... , 1.4398304...]])
"""
# *ForwardMatrix1* and *ForwardMatrix2* are not included in the camera
# profile.
if is_identity(M_forward_matrix_1) and is_identity(M_forward_matrix_2):
M_camera_to_XYZ = np.linalg.inv(
XYZ_to_camera_space_matrix(
xy, CCT_calibration_illuminant_1, CCT_calibration_illuminant_2,
M_color_matrix_1, M_color_matrix_2, M_camera_calibration_1,
M_camera_calibration_2, analog_balance))
M_CAT = chromatic_adaptation_matrix_VonKries(
xy_to_XYZ(xy), xy_to_XYZ(ADOBE_DNG_XYZ_ILLUMINANT),
chromatic_adaptation_transform)
M_camera_space_to_XYZ = dot_matrix(M_CAT, M_camera_to_XYZ)
else:
uv = UCS_to_uv(XYZ_to_UCS(xy_to_XYZ(xy)))
CCT, _D_uv = uv_to_CCT_Robertson1968(uv)
M_CC = interpolated_matrix(
CCT, CCT_calibration_illuminant_1, CCT_calibration_illuminant_2,
M_camera_calibration_1, M_camera_calibration_2)
# The reference implementation :cite:`AdobeSystems2015d` diverges from
# the white-paper :cite:`AdobeSystems2012f`:
# The reference implementation directly computes the camera neutral by
# multiplying directly the interpolated colour matrix :math:`CM` with
# the tristimulus values of the *xy* white balance chromaticity
# coordinates.
# The current implementation is based on the white-paper so that the
# interpolated camera calibration matrix :math:`CC` and the
# analog balance matrix :math:`AB` are accounted for.
camera_neutral = xy_to_camera_neutral(
xy, CCT_calibration_illuminant_1, CCT_calibration_illuminant_2,
M_color_matrix_1, M_color_matrix_2, M_camera_calibration_1,
M_camera_calibration_2, analog_balance)
M_AB = np.diagflat(analog_balance)
M_reference_neutral = dot_vector(
np.linalg.inv(dot_matrix(M_AB, M_CC)), camera_neutral)
M_D = np.linalg.inv(np.diagflat(M_reference_neutral))
M_FM = interpolated_matrix(CCT, CCT_calibration_illuminant_1,
CCT_calibration_illuminant_2,
M_forward_matrix_1, M_forward_matrix_2)
M_camera_space_to_XYZ = dot_matrix(
dot_matrix(M_FM, M_D), np.linalg.inv(dot_matrix(M_AB, M_CC)))
return M_camera_space_to_XYZ