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(1 / 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.exposure.common.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 luminance_average_key(image): """ Comparison key function. """ f_number = image.metadata.f_number exposure_time = image.metadata.exposure_time iso = image.metadata.iso if None in (f_number, exposure_time, iso): warning('"{0}" exposure data is missing, average luminance ' 'sorting is inapplicable!'.format(image.path)) return None return 1 / average_luminance(f_number, exposure_time, iso)
def exposure_value_100(N, t, S): """ Computes the exposure value :math:`EV100` from given relative aperture *F-Number* :math:`N`, *Exposure Time* :math:`t` and *ISO* arithmetic speed :math:`S`. Parameters ---------- N : array_like Relative aperture *F-Number* :math:`N`. t : array_like *Exposure Time* :math:`t`. S : array_like *ISO* arithmetic speed :math:`S`. Returns ------- ndarray Exposure value :math:`EV100`. References ---------- :cite:`ISO2006`, :cite:`Lagarde2014` Notes ----- - The underlying implementation uses the :func:`colour_hdri.luminance_to_exposure_value` and :func:`colour_hdri.average_luminance` definitions with same fixed value for the *reflected light calibration constant* :math:`k` which cancels its scaling effect and produces a value equal to :math:`log_2(\\cfrac{N^2}{t}) - log_2(\\cfrac{S}{100})` as given in :cite:`Lagarde2014`. Examples -------- >>> exposure_value_100(8, 1 / 250, 400) # doctest: +ELLIPSIS 11.9657842... """ return luminance_to_exposure_value(average_luminance(N, t, S), 100)
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(1 / 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 according. 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