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)
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
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
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