colour_hdri.calibration.debevec1997 — Colour - HDRI 0.1.0 documentation

# Source code for colour_hdri.calibration.debevec1997

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    """
    Debevec (1997) Camera Response Function Computation
    ===================================================

    Defines Debevec (1997) camera responses computation objects:

    -   :func:`g_solve`
    -   :func:`camera_response_functions_Debevec1997`

    See Also
    --------
    `Colour - HDRI - Example: Merge from Low Dynamic Range Files IPython Notebook
    <https://github.com/colour-science/colour-hdri/blob/master/colour_hdri/examples/examples_merge_from_ldr_files.ipynb>`_  # noqa

    References
    ----------
    .. [1]  Debevec, P., & Malik, J. (1997). Recovering High Dynamic Range Radiance
            Maps from Photographs, (August), 1–10. doi:10.1145/258734.258884
    """

    from __future__ import division, unicode_literals

    import numpy as np

    from colour import tstack

    from colour_hdri.generation import weighting_function_Debevec1997
    from colour_hdri.sampling import samples_Grossberg2003
    from colour_hdri.utilities import average_luminance

    __author__ = 'Colour Developers'
    __copyright__ = 'Copyright (C) 2015 - Colour Developers'
    __license__ = 'New BSD License - http://opensource.org/licenses/BSD-3-Clause'
    __maintainer__ = 'Colour Developers'
    __email__ = 'colour-science@googlegroups.com'
    __status__ = 'Production'

    __all__ = ['g_solve',
               'camera_response_functions_Debevec1997']

    def g_solve(Z, B, l=30, w=weighting_function_Debevec1997, n=256):
        """
        Given a set of pixel values observed for several pixels in several images
        with different exposure times, this function returns the imaging system’s
        response function :math:`g` as well as the log film irradiance values
        :math:`lE` for the observed pixels.

        Parameters
        ----------
        Z : array_like
            Set of pixel values observed for several pixels in several images.
        B : array_like
            Log :math:`\Delta t`, or log shutter speed for images.
        l : numeric, optional
            :math:`\lambda` smoothing term.
        w : callable, optional
            Weighting function :math:`w`.
        n : int, optional
            :math:`n` constant.

        Returns
        -------
        tuple
            Camera response functions :math:`g(z)` and log film irradiance values
            :math:`lE`.
        """

        Z = np.asarray(Z).astype(int)
        B = np.asarray(B)
        l = np.asarray(l)

        Z_x, Z_y = Z.shape

        A = np.zeros((Z_x * Z_y + n - 1, n + Z_x))
        b = np.zeros((A.shape[0], 1))
        w = w(np.linspace(0, 1, n))

        k = 0
        for i in np.arange(Z_x):
            for j in np.arange(Z_y):
                Z_c = Z[i, j]
                w_ij = w[Z_c]
                A[k, Z_c] = w_ij
                A[k, n + i] = -w_ij
                b[k] = w_ij * B[j]
                k += 1

        A[k, n / 2] = 1
        k += 1

        for i in np.arange(1, n - 1, 1):
            A[k, i - 1] = l * w[i]
            A[k, i + 0] = l * w[i] * -2
            A[k, i + 1] = l * w[i]
            k += 1

        x = np.squeeze(np.linalg.lstsq(A, b)[0])

        g = x[0:n]
        lE = x[n:x.shape[0]]

        return g, lE

    def camera_response_functions_Debevec1997(image_stack,
                                              s=samples_Grossberg2003,
                                              samples=1000,
                                              l=30,
                                              w=weighting_function_Debevec1997,
                                              n=256,
                                              normalise=True):
        """
        Returns the camera response functions for given image stack using Debevec
        (1997) method.

        Image channels are sampled with :math:`s` sampling function and the output
        samples are passed to :func:`g_solve`.

        Parameters
        ----------
        image_stack : ImageStack
            Stack of single channel or multi-channel floating point images.
        s : callable, optional
            Sampling function :math:`s`.
        samples : int, optional
            Samples count per images.
        l : numeric, optional
            :math:`\lambda` smoothing term.
        w : callable, optional
            Weighting function :math:`w`.
        n : int, optional
            :math:`n` constant.
        normalise : bool, optional
            Enables the camera response functions normalisation. Uncertain camera
            response functions values resulting from :math:`w` function are
            set to zero.

        Returns
        -------
        ndarray
            Camera response functions :math:`g(z)`.
        """

        samples = s(image_stack.data, samples, n)

        L_l = np.log(average_luminance(image_stack.f_number,
                                       image_stack.exposure_time,
                                       image_stack.iso))

        crfs = np.exp(tstack(np.array([g_solve(samples[..., x], L_l, l, w, n)[0]
                                       for x in range(samples.shape[-1])])))

        if normalise:
            # TODO: Investigate if the normalisation value should account for the
            # percentage of uncertain camera response functions values or be
            # correlated to it and scaled accordingly. As an alternative of setting
            # the uncertain camera response functions values to zero, it would be
            # interesting to explore extrapolation as the camera response functions
            # are essentially smooth. It is important to note that camera sensors
            # are usually acting non linearly when reaching saturation level.
            crfs[w(np.linspace(0, 1, crfs.shape[0])) == 0] = 0
            crfs /= np.max(crfs, axis=0)

        return crfs