htgop

Optical properties of a gas mixture
git clone git://git.meso-star.fr/htgop.git
Log | Files | Refs | README | LICENSE

commit 7ddf06d6bfa49fb5715422c86822c10f6cff4a62
parent 782c5a211aaf4a79c94bd5dee2935d328670daa3
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Mon,  6 Aug 2018 18:06:40 +0200

Implement and test htgop_get_sw_spectral_intervals_CIE_XYZ

Diffstat:
Mcmake/CMakeLists.txt | 2+-
Msrc/htgop.h | 6++++++
Dsrc/htgop_sample_sw_spectral_interval_CIE_1931_XYZ.c | 285-------------------------------------------------------------------------------
Asrc/htgop_sw_spectral_interval_CIE_XYZ.c | 336+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_htgop_load.c | 48++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 391 insertions(+), 286 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -42,7 +42,7 @@ set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) set(HTGOP_FILES_SRC htgop.c - htgop_sample_sw_spectral_interval_CIE_1931_XYZ.c) + htgop_sw_spectral_interval_CIE_XYZ.c) set(HTGOP_FILES_INC htgop_c.h htgop_fetch_radiative_properties.h diff --git a/src/htgop.h b/src/htgop.h @@ -363,6 +363,12 @@ htgop_spectral_interval_sample_quadrature const double r, /* Canonical random number in [0, 1[ */ size_t* iquad_point); /* Id of the sample quadrature point */ +/* Return the inclusive range of spectral interval that overlaps the CIE XYZ + * color space */ +HTGOP_API res_T +htgop_get_sw_spectral_intervals_CIE_XYZ + (const struct htgop* htgop, + size_t specint_raneg[2]); END_DECLS diff --git a/src/htgop_sample_sw_spectral_interval_CIE_1931_XYZ.c b/src/htgop_sample_sw_spectral_interval_CIE_1931_XYZ.c @@ -1,285 +0,0 @@ -/* Copyright (C) |Meso|Star> 2018 (contact@meso-star.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#define _POSIX_C_SOURCE 200112L /* nextafter support */ - -#include "htgop.h" -#include "htgop_c.h" - -#include <rsys/algorithm.h> - -/* Helper macros to convert from wave number to wavelength and vice versa */ -#define WNUM_TO_WLEN(Num) (1.0e7 / ((double)Num)) -#define WLEN_TO_WNUM(Len) WNUM_TO_WLEN(Len) - -/* Boundaries of the CIE XYZ color space in wavelength and wave number */ -#define CIE_XYZ_WAVELENGTH_MIN 380.0 /* In nanometer */ -#define CIE_XYZ_WAVELENGTH_MAX 780.0 /* In nanometer */ -#define CIE_XYZ_WAVENUMBER_MIN WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MAX) /*In cm^-1*/ -#define CIE_XYZ_WAVENUMBER_MAX WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MIN) /*In cm^-1*/ - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static FINLINE double -trapezoidal_integration - (const double lambda_lo, /* Integral lower bound. In nanometer */ - const double lambda_hi, /* Integral upper bound. In nanometer */ - double (*f_bar)(const double lambda)) /* Function to integrate */ -{ - double dlambda; - size_t i, n; - double integral = 0; - ASSERT(lambda_lo <= lambda_hi); - ASSERT(lambda_lo > 0); - - n = (size_t)(lambda_hi - lambda_lo) + 1; - dlambda = (lambda_hi - lambda_lo) / (double)n; - - FOR_EACH(i, 0, n) { - const double lambda1 = lambda_lo + dlambda*(double)(i+0); - const double lambda2 = lambda_hi + dlambda*(double)(i+1); - const double f1 = f_bar(lambda1); - const double f2 = f_bar(lambda2); - integral += (f1 + f2)*dlambda*0.5; - } - return integral; -} - -/* The following 3 functions are used to fit the CIE Xbar, Ybar and Zbar curved - * has defined by the 1931 standard. These analytical fits are propsed by C. - * Wyman, P. P. Sloan & P. Shirley in "Simple Analytic Approximations to the - * CIE XYZ Color Matching Functions" - JCGT 2013. */ -static FINLINE double -fit_x_bar_1931(const double lambda) -{ - const double a = (lambda - 442.0) * (lambda < 442.0 ? 0.0624 : 0.0374); - const double b = (lambda - 599.8) * (lambda < 599.8 ? 0.0264 : 0.0323); - const double c = (lambda - 501.1) * (lambda < 501.1 ? 0.0490 : 0.0382); - return 0.362*exp(-0.5*a*a) + 1.056*exp(-0.5f*b*b) - 0.065*exp(-0.5*c*c); -} - -static FINLINE double -fit_y_bar_1931(const double lambda) -{ - const double a = (lambda - 568.8) * (lambda < 568.8 ? 0.0213 : 0.0247); - const double b = (lambda - 530.9) * (lambda < 530.9 ? 0.0613 : 0.0322); - return 0.821*exp(-0.5*a*a) + 0.286*exp(-0.5*b*b); -} - -static FINLINE double -fit_z_bar_1931(const double lambda) -{ - const double a = (lambda - 437.0) * (lambda < 437.0 ? 0.0845 : 0.0278); - const double b = (lambda - 459.0) * (lambda < 459.0 ? 0.0385 : 0.0725); - return 1.217*exp(-0.5*a*a) + 0.681*exp(-0.5*b*b); -} - -static INLINE res_T -sample_sw_spectral_interval_CIE - (const struct htgop* htgop, - const double* cdf, - const size_t cdf_length, - const double r, /* Canonical number in [0, 1[ */ - size_t* id) -{ - double r_next = nextafter(r, DBL_MAX); - double* find; - size_t i; - ASSERT(htgop && cdf && cdf_length); - - if(!id) return RES_BAD_ARG; - if(r < 0 || r >= 1) { - log_err(htgop, "Could nont sample a spectral interval in the CIE XYZ color " - "space: invalid canonical random number `%g'.\n", r); - return RES_BAD_ARG; - } - - /* Use r_next rather than r in order to find the first entry that is not less - * than *or equal* to r */ - find = search_lower_bound(&r_next, cdf, cdf_length, sizeof(double), cmp_dbl); - if(!find) { /* <=> CIE XYZ band does not intersect the loaded intervals */ - log_err(htgop, "Could not sample a spectral interval in the CIE XYZ color " - "space : the loaded data does not overlap the CIE XYZ color space.\n"); - return RES_BAD_OP; - } - - i = (size_t)(find - cdf); - ASSERT(i < cdf_length && cdf[i] > r && (!i || cdf[i-1] <= r)); - *id = i; - return RES_OK; -} - -/******************************************************************************* - * Exported functions - ******************************************************************************/ -res_T -htgop_sample_sw_spectral_interval_CIE_1931_X - (const struct htgop* htgop, - const double r, /* Canonical number in [0, 1[ */ - size_t* ispecint) -{ - if(!htgop) return RES_BAD_ARG; - return sample_sw_spectral_interval_CIE - (htgop, darray_double_cdata_get(&htgop->sw_X_cdf), - darray_double_size_get(&htgop->sw_X_cdf), r, ispecint); -} - -res_T -htgop_sample_sw_spectral_interval_CIE_1931_Y - (const struct htgop* htgop, - const double r, /* Canonical number in [0, 1[ */ - size_t* ispecint) -{ - if(!htgop) return RES_BAD_ARG; - return sample_sw_spectral_interval_CIE - (htgop, darray_double_cdata_get(&htgop->sw_Y_cdf), - darray_double_size_get(&htgop->sw_Y_cdf), r, ispecint); -} - -res_T -htgop_sample_sw_spectral_interval_CIE_1931_Z - (const struct htgop* htgop, - const double r, /* Canonical number in [0, 1[ */ - size_t* ispecint) -{ - if(!htgop) return RES_BAD_ARG; - return sample_sw_spectral_interval_CIE - (htgop, darray_double_cdata_get(&htgop->sw_Z_cdf), - darray_double_size_get(&htgop->sw_Z_cdf), r, ispecint); -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -res_T -compute_CIE_1931_XYZ_cdf(struct htgop* htgop) -{ - enum { X, Y, Z }; /* Helper constant */ - size_t iband_min, iband_max; - const double* wnums = NULL; - double sum[3] = {0,0,0}; - double* pdf[3] = {NULL, NULL, NULL}; - double* cdf[3] = {NULL, NULL, NULL}; - size_t i, n; - res_T res = RES_OK; - ASSERT(htgop); - - HTGOP(get_sw_spectral_intervals_wave_numbers(htgop, &wnums)); - HTGOP(get_sw_spectral_intervals_count(htgop, &n)); - ASSERT(n); - - /* Allocate and reset the memory space for the tristimulus CDF */ - #define SETUP_STIMULUS(Stimulus) { \ - res = darray_double_resize(&htgop->sw_ ## Stimulus ## _cdf, n); \ - if(res != RES_OK) { \ - log_err(htgop, \ - "Could not reserve the memory space for the CDF " \ - "of the "STR(X)" stimulus.\n"); \ - goto error; \ - } \ - memset(darray_double_data_get(&htgop->sw_ ## Stimulus ## _cdf), 0, \ - n*sizeof(double)); \ - cdf[Stimulus] = darray_double_data_get(&htgop->sw_ ## Stimulus ## _cdf); \ - pdf[Stimulus] = cdf[Stimulus]; \ - } (void)0 - SETUP_STIMULUS(X); - SETUP_STIMULUS(Y); - SETUP_STIMULUS(Z); - #undef RESERVE - - ASSERT(eq_eps(CIE_XYZ_WAVENUMBER_MAX, WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MIN), 1.e-6)); - ASSERT(eq_eps(CIE_XYZ_WAVENUMBER_MIN, WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MAX), 1.e-6)); - ASSERT(eq_eps(CIE_XYZ_WAVELENGTH_MAX, WNUM_TO_WLEN(CIE_XYZ_WAVENUMBER_MIN), 1.e-6)); - ASSERT(eq_eps(CIE_XYZ_WAVELENGTH_MIN, WNUM_TO_WLEN(CIE_XYZ_WAVENUMBER_MAX), 1.e-6)); - - /* The sw spectral intervals does not intersect the CIE XYZ interval */ - if(wnums[0] > CIE_XYZ_WAVENUMBER_MAX || wnums[n] < CIE_XYZ_WAVENUMBER_MIN) { - log_warn(htgop, - "The loaded SW spectral intervals does not intersect " - "the CIE XYZ interval.\n"); - goto exit; - } - - /* Search the lower and upper bound of the bands that overlap the XYZ - * spectral interval. Use a simple linear search since the overall number of - * bands should be small */ - iband_min = 0, iband_max = n-1; - FOR_EACH(i, 0, n) { - if(wnums[i]<=CIE_XYZ_WAVENUMBER_MIN && CIE_XYZ_WAVENUMBER_MIN<=wnums[i+1]) - iband_min = i; - if(wnums[i]<=CIE_XYZ_WAVENUMBER_MAX && CIE_XYZ_WAVENUMBER_MAX<=wnums[i+1]) - iband_max = i; - } - ASSERT(iband_min <= iband_max); - - /* Compute the *unormalized* pdf of the tristimulus */ - FOR_EACH(i, iband_min, iband_max+1) { - const double lambda_lo = WNUM_TO_WLEN(wnums[i+1]); - const double lambda_hi = WNUM_TO_WLEN(wnums[i+0]); - ASSERT(lambda_lo <= lambda_hi); - pdf[X][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_x_bar_1931); - pdf[Y][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_y_bar_1931); - pdf[Z][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_z_bar_1931); - sum[X] += pdf[X][i]; - sum[Y] += pdf[Y][i]; - sum[Z] += pdf[Z][i]; - } - - /* Compute the cdf of the tristimulus. Note that the upper bound is 'n' and not - * 'iband_max+1' in order to ensure that the CDF is strictly increasing. */ - FOR_EACH(i, iband_min, n) { - /* Normalize the pdf */ - pdf[X][i] /= sum[X]; - pdf[Y][i] /= sum[Y]; - pdf[Z][i] /= sum[Z]; - /* Setup the cumulative */ - if(i == 0) { - cdf[X][i] = pdf[X][i]; - cdf[Y][i] = pdf[Y][i]; - cdf[Z][i] = pdf[Z][i]; - } else { - cdf[X][i] = pdf[X][i] + cdf[X][i-1]; - cdf[Y][i] = pdf[Y][i] + cdf[Y][i-1]; - cdf[Z][i] = pdf[Z][i] + cdf[Z][i-1]; - } - } - ASSERT(eq_eps(cdf[X][n-1], 1, 1.e-6)); - ASSERT(eq_eps(cdf[Y][n-1], 1, 1.e-6)); - ASSERT(eq_eps(cdf[Z][n-1], 1, 1.e-6)); - -#ifndef NDEBUG - FOR_EACH(i, 1, n) { - ASSERT(cdf[X][i] >= cdf[X][i-1]); - ASSERT(cdf[Y][i] >= cdf[Y][i-1]); - ASSERT(cdf[Z][i] >= cdf[Z][i-1]); - } -#endif - - /* Handle numerical issue */ - cdf[X][n-1] = 1.0; - cdf[Y][n-1] = 1.0; - cdf[Z][n-1] = 1.0; - -exit: - return res; -error: - darray_double_clear(&htgop->sw_X_cdf); - darray_double_clear(&htgop->sw_Y_cdf); - darray_double_clear(&htgop->sw_Z_cdf); - goto exit; -} - - diff --git a/src/htgop_sw_spectral_interval_CIE_XYZ.c b/src/htgop_sw_spectral_interval_CIE_XYZ.c @@ -0,0 +1,336 @@ +/* Copyright (C) |Meso|Star> 2018 (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#define _POSIX_C_SOURCE 200112L /* nextafter support */ + +#include "htgop.h" +#include "htgop_c.h" + +#include <rsys/algorithm.h> + +/* Helper macros to convert from wave number to wavelength and vice versa */ +#define WNUM_TO_WLEN(Num) (1.0e7 / ((double)Num)) +#define WLEN_TO_WNUM(Len) WNUM_TO_WLEN(Len) + +/* Boundaries of the CIE XYZ color space in wavelength and wave number */ +#define CIE_XYZ_WAVELENGTH_MIN 380.0 /* In nanometer */ +#define CIE_XYZ_WAVELENGTH_MAX 780.0 /* In nanometer */ +#define CIE_XYZ_WAVENUMBER_MIN WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MAX) /*In cm^-1*/ +#define CIE_XYZ_WAVENUMBER_MAX WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MIN) /*In cm^-1*/ + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static FINLINE double +trapezoidal_integration + (const double lambda_lo, /* Integral lower bound. In nanometer */ + const double lambda_hi, /* Integral upper bound. In nanometer */ + double (*f_bar)(const double lambda)) /* Function to integrate */ +{ + double dlambda; + size_t i, n; + double integral = 0; + ASSERT(lambda_lo <= lambda_hi); + ASSERT(lambda_lo > 0); + + n = (size_t)(lambda_hi - lambda_lo) + 1; + dlambda = (lambda_hi - lambda_lo) / (double)n; + + FOR_EACH(i, 0, n) { + const double lambda1 = lambda_lo + dlambda*(double)(i+0); + const double lambda2 = lambda_hi + dlambda*(double)(i+1); + const double f1 = f_bar(lambda1); + const double f2 = f_bar(lambda2); + integral += (f1 + f2)*dlambda*0.5; + } + return integral; +} + +/* The following 3 functions are used to fit the CIE Xbar, Ybar and Zbar curved + * has defined by the 1931 standard. These analytical fits are propsed by C. + * Wyman, P. P. Sloan & P. Shirley in "Simple Analytic Approximations to the + * CIE XYZ Color Matching Functions" - JCGT 2013. */ +static FINLINE double +fit_x_bar_1931(const double lambda) +{ + const double a = (lambda - 442.0) * (lambda < 442.0 ? 0.0624 : 0.0374); + const double b = (lambda - 599.8) * (lambda < 599.8 ? 0.0264 : 0.0323); + const double c = (lambda - 501.1) * (lambda < 501.1 ? 0.0490 : 0.0382); + return 0.362*exp(-0.5*a*a) + 1.056*exp(-0.5f*b*b) - 0.065*exp(-0.5*c*c); +} + +static FINLINE double +fit_y_bar_1931(const double lambda) +{ + const double a = (lambda - 568.8) * (lambda < 568.8 ? 0.0213 : 0.0247); + const double b = (lambda - 530.9) * (lambda < 530.9 ? 0.0613 : 0.0322); + return 0.821*exp(-0.5*a*a) + 0.286*exp(-0.5*b*b); +} + +static FINLINE double +fit_z_bar_1931(const double lambda) +{ + const double a = (lambda - 437.0) * (lambda < 437.0 ? 0.0845 : 0.0278); + const double b = (lambda - 459.0) * (lambda < 459.0 ? 0.0385 : 0.0725); + return 1.217*exp(-0.5*a*a) + 0.681*exp(-0.5*b*b); +} + +static INLINE res_T +sample_sw_spectral_interval_CIE + (const struct htgop* htgop, + const double* cdf, + const size_t cdf_length, + const double r, /* Canonical number in [0, 1[ */ + size_t* id) +{ + double r_next = nextafter(r, DBL_MAX); + double* find; + size_t i; + ASSERT(htgop && cdf && cdf_length); + + if(!id) return RES_BAD_ARG; + if(r < 0 || r >= 1) { + log_err(htgop, "Could nont sample a spectral interval in the CIE XYZ color " + "space: invalid canonical random number `%g'.\n", r); + return RES_BAD_ARG; + } + + /* Use r_next rather than r in order to find the first entry that is not less + * than *or equal* to r */ + find = search_lower_bound(&r_next, cdf, cdf_length, sizeof(double), cmp_dbl); + if(!find) { /* <=> CIE XYZ band does not intersect the loaded intervals */ + log_err(htgop, "Could not sample a spectral interval in the CIE XYZ color " + "space : the loaded data do not overlap the CIE XYZ color space.\n"); + return RES_BAD_OP; + } + + i = (size_t)(find - cdf); + ASSERT(i < cdf_length && cdf[i] > r && (!i || cdf[i-1] <= r)); + *id = i; + return RES_OK; +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +htgop_sample_sw_spectral_interval_CIE_1931_X + (const struct htgop* htgop, + const double r, /* Canonical number in [0, 1[ */ + size_t* ispecint) +{ + if(!htgop) return RES_BAD_ARG; + return sample_sw_spectral_interval_CIE + (htgop, darray_double_cdata_get(&htgop->sw_X_cdf), + darray_double_size_get(&htgop->sw_X_cdf), r, ispecint); +} + +res_T +htgop_sample_sw_spectral_interval_CIE_1931_Y + (const struct htgop* htgop, + const double r, /* Canonical number in [0, 1[ */ + size_t* ispecint) +{ + if(!htgop) return RES_BAD_ARG; + return sample_sw_spectral_interval_CIE + (htgop, darray_double_cdata_get(&htgop->sw_Y_cdf), + darray_double_size_get(&htgop->sw_Y_cdf), r, ispecint); +} + +res_T +htgop_sample_sw_spectral_interval_CIE_1931_Z + (const struct htgop* htgop, + const double r, /* Canonical number in [0, 1[ */ + size_t* ispecint) +{ + if(!htgop) return RES_BAD_ARG; + return sample_sw_spectral_interval_CIE + (htgop, darray_double_cdata_get(&htgop->sw_Z_cdf), + darray_double_size_get(&htgop->sw_Z_cdf), r, ispecint); +} + +res_T +htgop_get_sw_spectral_intervals_CIE_XYZ + (const struct htgop* htgop, + size_t specint_range[2]) +{ + const double* wnums = NULL; + const double wnum_min = nextafter(CIE_XYZ_WAVENUMBER_MIN, DBL_MAX); + const double wnum_max = CIE_XYZ_WAVENUMBER_MAX; + size_t nwnums; + res_T res = RES_OK; + double* low = NULL; + double* upp = NULL; + if(!htgop || !specint_range) return RES_BAD_ARG; + + wnums = darray_double_cdata_get(&htgop->sw_specints.wave_numbers); + nwnums = darray_double_size_get(&htgop->sw_specints.wave_numbers); + + low = search_lower_bound(&wnum_min, wnums, nwnums, sizeof(double), cmp_dbl); + upp = search_lower_bound(&wnum_max, wnums, nwnums, sizeof(double), cmp_dbl); + + if(!low || upp == wnums) { + log_err(htgop, + "%s: the loaded data do not overlap the CIE XYZ color space.\n", + FUNC_NAME); + res = RES_BAD_OP; + goto error; + } + ASSERT(low[0] >= wnum_min && (low == wnums || low[-1] < wnum_min)); + ASSERT(!upp || (upp[0] >= wnum_max && upp[-1] < wnum_max)); + + specint_range[0] = low == wnums ? 0 : (size_t)(low - wnums) - 1; + specint_range[1] = !upp ? nwnums - 1 : (size_t)(upp - wnums); + /* Transform the upper wnum bound in spectral interval id */ + specint_range[1] = specint_range[1] - 1; + + ASSERT(specint_range[0] < specint_range[1]); + ASSERT(wnums[specint_range[0]+0] <= CIE_XYZ_WAVENUMBER_MIN); + ASSERT(wnums[specint_range[0]+1] > CIE_XYZ_WAVENUMBER_MIN); + ASSERT(wnums[specint_range[1]+0] < CIE_XYZ_WAVENUMBER_MAX); + ASSERT(wnums[specint_range[1]+1] >= CIE_XYZ_WAVENUMBER_MAX); + +exit: + return res; +error: + if(specint_range) { + specint_range[0] = SIZE_MAX; + specint_range[1] = 0; + } + goto exit; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +compute_CIE_1931_XYZ_cdf(struct htgop* htgop) +{ + enum { X, Y, Z }; /* Helper constant */ + size_t iband_min, iband_max; + const double* wnums = NULL; + double sum[3] = {0,0,0}; + double* pdf[3] = {NULL, NULL, NULL}; + double* cdf[3] = {NULL, NULL, NULL}; + size_t i, n; + res_T res = RES_OK; + ASSERT(htgop); + + HTGOP(get_sw_spectral_intervals_wave_numbers(htgop, &wnums)); + HTGOP(get_sw_spectral_intervals_count(htgop, &n)); + ASSERT(n); + + /* Allocate and reset the memory space for the tristimulus CDF */ + #define SETUP_STIMULUS(Stimulus) { \ + res = darray_double_resize(&htgop->sw_ ## Stimulus ## _cdf, n); \ + if(res != RES_OK) { \ + log_err(htgop, \ + "Could not reserve the memory space for the CDF " \ + "of the "STR(X)" stimulus.\n"); \ + goto error; \ + } \ + memset(darray_double_data_get(&htgop->sw_ ## Stimulus ## _cdf), 0, \ + n*sizeof(double)); \ + cdf[Stimulus] = darray_double_data_get(&htgop->sw_ ## Stimulus ## _cdf); \ + pdf[Stimulus] = cdf[Stimulus]; \ + } (void)0 + SETUP_STIMULUS(X); + SETUP_STIMULUS(Y); + SETUP_STIMULUS(Z); + #undef RESERVE + + ASSERT(eq_eps(CIE_XYZ_WAVENUMBER_MAX, WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MIN), 1.e-6)); + ASSERT(eq_eps(CIE_XYZ_WAVENUMBER_MIN, WLEN_TO_WNUM(CIE_XYZ_WAVELENGTH_MAX), 1.e-6)); + ASSERT(eq_eps(CIE_XYZ_WAVELENGTH_MAX, WNUM_TO_WLEN(CIE_XYZ_WAVENUMBER_MIN), 1.e-6)); + ASSERT(eq_eps(CIE_XYZ_WAVELENGTH_MIN, WNUM_TO_WLEN(CIE_XYZ_WAVENUMBER_MAX), 1.e-6)); + + /* The sw spectral intervals does not intersect the CIE XYZ interval */ + if(wnums[0] > CIE_XYZ_WAVENUMBER_MAX || wnums[n] < CIE_XYZ_WAVENUMBER_MIN) { + log_warn(htgop, + "The loaded SW spectral intervals does not intersect " + "the CIE XYZ interval.\n"); + goto exit; + } + + /* Search the lower and upper bound of the bands that overlap the XYZ + * spectral interval. Use a simple linear search since the overall number of + * bands should be small */ + iband_min = 0, iband_max = n-1; + FOR_EACH(i, 0, n) { + if(wnums[i]<=CIE_XYZ_WAVENUMBER_MIN && CIE_XYZ_WAVENUMBER_MIN<=wnums[i+1]) + iband_min = i; + if(wnums[i]<=CIE_XYZ_WAVENUMBER_MAX && CIE_XYZ_WAVENUMBER_MAX<=wnums[i+1]) + iband_max = i; + } + ASSERT(iband_min <= iband_max); + + /* Compute the *unormalized* pdf of the tristimulus */ + FOR_EACH(i, iband_min, iband_max+1) { + const double lambda_lo = WNUM_TO_WLEN(wnums[i+1]); + const double lambda_hi = WNUM_TO_WLEN(wnums[i+0]); + ASSERT(lambda_lo <= lambda_hi); + pdf[X][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_x_bar_1931); + pdf[Y][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_y_bar_1931); + pdf[Z][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_z_bar_1931); + sum[X] += pdf[X][i]; + sum[Y] += pdf[Y][i]; + sum[Z] += pdf[Z][i]; + } + + /* Compute the cdf of the tristimulus. Note that the upper bound is 'n' and not + * 'iband_max+1' in order to ensure that the CDF is strictly increasing. */ + FOR_EACH(i, iband_min, n) { + /* Normalize the pdf */ + pdf[X][i] /= sum[X]; + pdf[Y][i] /= sum[Y]; + pdf[Z][i] /= sum[Z]; + /* Setup the cumulative */ + if(i == 0) { + cdf[X][i] = pdf[X][i]; + cdf[Y][i] = pdf[Y][i]; + cdf[Z][i] = pdf[Z][i]; + } else { + cdf[X][i] = pdf[X][i] + cdf[X][i-1]; + cdf[Y][i] = pdf[Y][i] + cdf[Y][i-1]; + cdf[Z][i] = pdf[Z][i] + cdf[Z][i-1]; + } + } + ASSERT(eq_eps(cdf[X][n-1], 1, 1.e-6)); + ASSERT(eq_eps(cdf[Y][n-1], 1, 1.e-6)); + ASSERT(eq_eps(cdf[Z][n-1], 1, 1.e-6)); + +#ifndef NDEBUG + FOR_EACH(i, 1, n) { + ASSERT(cdf[X][i] >= cdf[X][i-1]); + ASSERT(cdf[Y][i] >= cdf[Y][i-1]); + ASSERT(cdf[Z][i] >= cdf[Z][i-1]); + } +#endif + + /* Handle numerical issue */ + cdf[X][n-1] = 1.0; + cdf[Y][n-1] = 1.0; + cdf[Z][n-1] = 1.0; + +exit: + return res; +error: + darray_double_clear(&htgop->sw_X_cdf); + darray_double_clear(&htgop->sw_Y_cdf); + darray_double_clear(&htgop->sw_Z_cdf); + goto exit; +} + + diff --git a/src/test_htgop_load.c b/src/test_htgop_load.c @@ -64,6 +64,53 @@ check_position_to_layer_id(const struct htgop* htgop) } } +static void +check_sw_specints_CIE_XYZ(const struct htgop* htgop) +{ + struct htgop_spectral_interval specint_low; + struct htgop_spectral_interval specint_upp; + const double wnum_min_CIE_XYZ = 1.e7/780.0; + const double wnum_max_CIE_XYZ = 1.e7/380.0; + size_t range[2]; + size_t range2[2]; + size_t i; + size_t nspecints; + + CHK(htgop_get_sw_spectral_intervals_CIE_XYZ(NULL, range) == RES_BAD_ARG); + CHK(htgop_get_sw_spectral_intervals_CIE_XYZ(htgop, NULL) == RES_BAD_ARG); + CHK(htgop_get_sw_spectral_intervals_CIE_XYZ(htgop, range) == RES_OK); + CHK(range[0] <= range[1]); + + CHK(htgop_get_sw_spectral_interval(htgop, range[0], &specint_low) == RES_OK); + CHK(htgop_get_sw_spectral_interval(htgop, range[1], &specint_upp) == RES_OK); + + CHK(specint_low.wave_numbers[0] < specint_upp.wave_numbers[1]); + CHK(specint_low.wave_numbers[0] <= wnum_min_CIE_XYZ); + CHK(specint_upp.wave_numbers[1] >= wnum_max_CIE_XYZ); + + CHK(htgop_get_sw_spectral_intervals_count(htgop, &nspecints) == RES_OK); + range2[0] = SIZE_MAX; + range2[1] = SIZE_MAX; + FOR_EACH(i, 0, nspecints) { + struct htgop_spectral_interval specint; + CHK(htgop_get_sw_spectral_interval(htgop, i, &specint) == RES_OK); + + if(specint.wave_numbers[0] <= wnum_min_CIE_XYZ + && specint.wave_numbers[1] > wnum_min_CIE_XYZ) { + range2[0] = i; + } + if(specint.wave_numbers[0] < wnum_max_CIE_XYZ + && specint.wave_numbers[1] >= wnum_max_CIE_XYZ) { + range2[1] = i; + } + } + CHK(range2[0] != SIZE_MAX); + CHK(range2[1] != SIZE_MAX); + CHK(range2[0] <= range2[1]); + CHK(range2[0] == range[0]); + CHK(range2[1] == range[1]); +} + int main(int argc, char** argv) { @@ -359,6 +406,7 @@ main(int argc, char** argv) } check_position_to_layer_id(htgop); + check_sw_specints_CIE_XYZ(htgop); CHK(fclose(fp) == 0); reader_release(&rdr);