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
    def test_g_solve(self):
        """
        Tests :func:`colour_hdri.calibration.debevec1997.g_solve` definition.
        """

        image_stack = ImageStack.from_files(JPG_IMAGES)
        L_l = np.log(average_luminance(image_stack.f_number,
                                       image_stack.exposure_time,
                                       image_stack.iso))
        samples = samples_Grossberg2003(image_stack.data)

        for i in range(3):
            g, lE = g_solve(samples[..., i], L_l)

            # Lower precision for unit tests under *travis-ci*.
            np.testing.assert_allclose(
                g[0:-2],
                np.load(os.path.join(
                    CALIBRATION_DIRECTORY,
                    'test_g_solve_g_{0}.npy'.format(i)))[0:-2],
                rtol=0.001,
                atol=0.001)

            # Lower precision for unit tests under *travis-ci*.
            np.testing.assert_allclose(
                lE[1:],
                np.load(os.path.join(
                    CALIBRATION_DIRECTORY,
                    'test_g_solve_lE_{0}.npy'.format(i)))[1:],
                rtol=0.001,
                atol=0.001)
Exemple #3
0
    def test_g_solve(self):
        """
        Tests :func:`colour_hdri.calibration.debevec1997.g_solve` definition.
        """

        image_stack = ImageStack.from_files(JPG_IMAGES)
        L_l = np.log(
            average_luminance(image_stack.f_number, image_stack.exposure_time,
                              image_stack.iso))
        samples = samples_Grossberg2003(image_stack.data)

        for i in range(3):
            g, lE = g_solve(samples[..., i], L_l)

            # Lower precision for unit tests under *travis-ci*.
            np.testing.assert_allclose(
                g[0:-2],
                np.load(
                    os.path.join(CALIBRATION_DIRECTORY,
                                 'test_g_solve_g_{0}.npy'.format(i)))[0:-2],
                rtol=0.001,
                atol=0.001)

            # Lower precision for unit tests under *travis-ci*.
            np.testing.assert_allclose(
                lE[1:],
                np.load(
                    os.path.join(CALIBRATION_DIRECTORY,
                                 'test_g_solve_lE_{0}.npy'.format(i)))[1:],
                rtol=0.001,
                atol=0.001)
    def test_average_luminance(self):
        """
        Tests :func:`colour_hdri.utilities.exposure.average_luminance`
        definition.
        """

        np.testing.assert_almost_equal(
            average_luminance(np.array([2.8, 5.6, 8]),
                              np.array([0.125, 0.5, 1.0]),
                              np.array([100, 800, 16000])),
            np.array([0.127551, 1.0204082, 20.]),
            decimal=7)
    def test_average_luminance(self):
        """
        Tests :func:`colour_hdri.utilities.exposure.average_luminance`
        definition.
        """

        np.testing.assert_almost_equal(
            average_luminance(
                np.array([2.8, 5.6, 8]),
                np.array([0.125, 0.5, 1.0]),
                np.array([100, 800, 16000])),
            np.array([0.12755102, 1.02040816, 20.]),
            decimal=7)  # yapf: disable
    def test_average_luminance(self):
        """
        Tests :func:`colour_hdri.utilities.exposure.average_luminance`
        definition.
        """

        np.testing.assert_almost_equal(
            average_luminance(
                np.array([2.8, 5.6, 8]),
                np.array([0.125, 0.5, 1.0]),
                np.array([100, 800, 16000]),
            ),
            np.array([7.84000000, 0.98000000, 0.05000000]),
            decimal=7)
def image_stack_to_radiance_image(
        image_stack,
        weighting_function=weighting_function_Debevec1997,
        weighting_average=False,
        camera_response_functions=None):
    """
    Generates a HDRI / radiance image from given image stack.

    Parameters
    ----------
    image_stack : ImageStack
        Stack of single channel or multi-channel floating point images. The
        stack is assumed to be representing linear values except if
        ``camera_response_functions`` argument is provided.
    weighting_function : callable, optional
        Weighting function :math:`w`.
    weighting_average : bool, optional
         Enables weighting function :math:`w` computation on channels average
         instead of on a per channel basis.
    camera_response_functions : array_like, optional
        Camera response functions :math:`g(z)` of the imaging system / camera
        if the stack is representing non linear values.

    Returns
    -------
    ndarray
        Radiance image.
    """

    image_c = None
    weight_c = None
    for image in image_stack:
        if image_c is None:
            image_c = np.zeros(image.data.shape)
            weight_c = np.zeros(image.data.shape)

        L = average_luminance(
            image.metadata.f_number,
            image.metadata.exposure_time,
            image.metadata.iso)

        if weighting_average and image.data.ndim == 3:
            weight = weighting_function(np.average(image.data, axis=-1))
            weight = np.rollaxis(weight[np.newaxis], 0, 3)
        else:
            weight = weighting_function(image.data)

        image_data = image.data
        if camera_response_functions is not None:
            samples = np.linspace(0, 1, camera_response_functions.shape[0])

            R, G, B = tsplit(image.data)
            R = np.interp(R, samples, camera_response_functions[..., 0])
            G = np.interp(G, samples, camera_response_functions[..., 1])
            B = np.interp(B, samples, camera_response_functions[..., 2])
            image_data = tstack((R, G, B))

        image_c += weight * image_data / L
        weight_c += weight

    if image_c is not None:
        image_c /= weight_c
        image_c[np.isnan(image_c)] = 0

    return image_c
Exemple #8
0
def image_stack_to_radiance_image(
        image_stack,
        weighting_function=weighting_function_Debevec1997,
        weighting_average=False,
        camera_response_functions=None):
    """
    Generates a HDRI / radiance image from given image stack.

    Parameters
    ----------
    image_stack : colour_hdri.ImageStack
        Stack of single channel or multi-channel floating point images. The
        stack is assumed to be representing linear values except if
        ``camera_response_functions`` argument is provided.
    weighting_function : callable, optional
        Weighting function :math:`w`.
    weighting_average : bool, optional
         Enables weighting function :math:`w` computation on channels average
         instead of on a per channel basis.
    camera_response_functions : array_like, optional
        Camera response functions :math:`g(z)` of the imaging system / camera
        if the stack is representing non linear values.

    Returns
    -------
    ndarray
        Radiance image.

    Warning
    -------
    If the image stack contains images with negative or equal to zero values,
    unpredictable results may occur and NaNs might be generated. It is
    thus recommended to encode the images in a wider RGB colourspace or clamp
    negative values.

    References
    ----------
    :cite:`Banterle2011n`
    """

    image_c = None
    weight_c = None
    for i, image in enumerate(image_stack):
        if image_c is None:
            image_c = np.zeros(image.data.shape)
            weight_c = np.zeros(image.data.shape)

        L = 1 / average_luminance(image.metadata.f_number,
                                  image.metadata.exposure_time,
                                  image.metadata.iso)

        if np.any(image.data <= 0):
            warning('"{0}" image channels contain negative or equal to zero '
                    'values, unpredictable results may occur! Please consider '
                    'encoding your images in a wider gamut RGB colourspace or '
                    'clamp negative values.'.format(image.path))

        if weighting_average and image.data.ndim == 3:
            average = np.average(image.data, axis=-1)

            weights = weighting_function(average)
            weights = np.rollaxis(weights[np.newaxis], 0, 3)
            if i == 0:
                weights[average >= 0.5] = 1
            if i == len(image_stack) - 1:
                weights[average <= 0.5] = 1
        else:
            weights = weighting_function(image.data)
            if i == 0:
                weights[image.data >= 0.5] = 1
            if i == len(image_stack) - 1:
                weights[image.data <= 0.5] = 1

        image_data = image.data
        if camera_response_functions is not None:
            samples = np.linspace(0, 1, camera_response_functions.shape[0])

            R, G, B = tsplit(image.data)
            R = np.interp(R, samples, camera_response_functions[..., 0])
            G = np.interp(G, samples, camera_response_functions[..., 1])
            B = np.interp(B, samples, camera_response_functions[..., 2])
            image_data = tstack([R, G, B])

        image_c += weights * image_data / L
        weight_c += weights

    if image_c is not None:
        image_c /= weight_c

    return image_c
Exemple #9
0
def camera_response_functions_Debevec1997(image_stack,
                                          s=samples_Grossberg2003,
                                          samples=1000,
                                          l_s=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:`colour_hdri.g_solve`.

    Parameters
    ----------
    image_stack : colour_hdri.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_s : 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)`.

    References
    ----------
    -   :cite:`Debevec1997a`
    """

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

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

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

    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