Source code for colour_hdri.models.dng

# -*- 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