/* $Id: cmsscn.c,v 1.59 2008/02/19 21:02:39 hvengel Exp $ */
/* Little cms - profiler construction set */
/* Copyright (C) 1998-2001 Marti Maria */
/* Copyright (C) 2005-2006 Hal Engel */
/* Copyright (C) 2006 Gerhard Fuernkranz */
/*
THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.

IN NO EVENT SHALL THE DEVELOPERS BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
OF THIS SOFTWARE.

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/>.
*/
/* Version 1.11.3 */

#include "lcmsprf.h"
#include <stdio.h>
#include <sys/stat.h>

#if !defined(__WIN32__) && (defined(_WIN32) || defined(WIN32))
#define __WIN32__
#endif

#if defined( __WIN32__ )
#include <io.h>
#include <malloc.h>
#endif

#include <stdlib.h>

#include "rspl.h"

#define ALWAYS

#define RVEC_STD 0.1
#define RVEC_VAR (RVEC_STD*RVEC_STD)

#define sqr(x) ((x)*(x))

/* The scanner profiler */

BOOL   cdecl cmsxScannerProfilerInit(LPPROFILERDATA sys);
BOOL   cdecl cmsxScannerProfilerDo(LPPROFILERDATA sys);

/* dummy for cmsprintf */

static int
printfDummy(const char *format, int parms, char typ, ...) { return 0; }

/* convert cow to co */

static co
cow2co(cow in)
{
    co out;

    out.p[0] = in.p[0]; out.p[1] = in.p[1]; out.p[2] = in.p[2];
    out.v[0] = in.v[0]; out.v[1] = in.v[1]; out.v[2] = in.v[2];

    return out;
}

/*
 * convert XYZ noise to Lab noise w/ monte carlo simulation,
 *
 * fixme: should do the conversion analytically
 * MSE_Lab = trace(M'*M), where
 * M = diag(StdXYZ) * J and
 * J is the Jacobian matrix dLab/dXYZ
 */

static void
ComputeNoiseXYZ2Lab(LPcmsCIEXYZ pXYZ,
		    LPcmsCIEXYZ pStdXYZ,
		    LPcmsCIELab pStdLab)
{
    int i, niter = 1000;
    cmsCIEXYZ XYZ;
    cmsCIELab Lab, Lab2, MSE;

    cmsXYZ2Lab(cmsD50_XYZ(), &Lab, pXYZ);
    MSE.L = MSE.a = MSE.b = 0;

    for (i = 0; i < niter; i++) {
    	XYZ = *pXYZ;
    	XYZ.X += pStdXYZ->X * cmsxRandn();
    	XYZ.Y += pStdXYZ->Y * cmsxRandn();
    	XYZ.Z += pStdXYZ->Z * cmsxRandn();
	cmsXYZ2Lab(cmsD50_XYZ(), &Lab2, &XYZ);
	MSE.L += (Lab2.L-Lab.L) * (Lab2.L-Lab.L);
	MSE.a += (Lab2.a-Lab.a) * (Lab2.a-Lab.a);
	MSE.b += (Lab2.b-Lab.b) * (Lab2.b-Lab.b);
    }

    pStdLab->L = sqrt(MSE.L / niter);
    pStdLab->a = sqrt(MSE.a / niter);
    pStdLab->b = sqrt(MSE.b / niter);
}

/* convert RGB noise to Lab noise w/ monte carlo simulation */

static void
ComputeNoiseRGB2Lab(rspl *spline, cow *RGB,
		    double StdRGB[3], LPcmsCIELab pStdLab)
{
    co pt, pt2;
    int i, niter = 1000;
    cmsCIELab MSE;

    pt = cow2co(*RGB);
    spline->interp(spline, &pt);
    MSE.L = MSE.a = MSE.b = 0;

    for (i = 0; i < niter; i++) {
	pt2 = cow2co(*RGB);
	pt2.p[0] += StdRGB[0] * cmsxRandn();
	pt2.p[1] += StdRGB[1] * cmsxRandn();
	pt2.p[2] += StdRGB[2] * cmsxRandn();
	spline->interp(spline, &pt2);
	MSE.L += (pt2.v[0]-pt.v[0]) * (pt2.v[0]-pt.v[0]);
	MSE.a += (pt2.v[1]-pt.v[1]) * (pt2.v[1]-pt.v[1]);
	MSE.b += (pt2.v[2]-pt.v[2]) * (pt2.v[2]-pt.v[2]);
    }

    pStdLab->L = sqrt(MSE.L / niter);
    pStdLab->a = sqrt(MSE.a / niter);
    pStdLab->b = sqrt(MSE.b / niter);
}

/* Create regression matrix */

static void
ComputeGlobalRegression(LPPROFILERDATA sys)
{
    rspl         *spline;
    rspl         *cv_spline;
    int          i, j, n, n95;
    int          gres[MXDI];
    datai        low     = {  0,    0,    0};
    datai        high    = {255,  255,  255};
    datao        vlow    = {  0, -128, -128};
    datao        vhigh   = {100,  128,  128};
    char         **names = alloca((sys->hdr.m.nPatches+10) * sizeof(char*));
    cow          *data   = alloca((sys->hdr.m.nPatches+10) * sizeof(cow));
    cow          *train  = alloca((sys->hdr.m.nPatches+10) * sizeof(cow));
    SETOFPATCHES test    = alloca((sys->hdr.m.nPatches+10) * sizeof(BOOL));
    double       *dE94   = alloca(sys->hdr.m.nPatches * sizeof(double));
    double       *dE     = alloca(sys->hdr.m.nPatches * sizeof(double));
    double       sumdE, sumdE94, sumdEsq, sumdE94sq, med, mad;
    double       avgDev[3];
    double       smoothness = sys->RsplSmoothness;
    double       weight_sum;
    co           point;

    avgDev[0] = avgDev[1] = avgDev[2] = 0.0025;
    /* smoothness = 100*(smoothness-1); */
    smoothness = pow(10.0, smoothness/2.0);

#ifdef NEVER
    {
	int nTerms;
	BOOL lAllOk;

	/* sanity check on patch data */
	/* will issue a warning message if data does nto seem OK */
	/* Will only catch very bad problems with data */

	nTerms = cmsxFindOptimumNumOfTerms(&sys ->hdr, 55, &lAllOk);
	if (!lAllOk)
	{
	    sys->hdr.cmsprintf("*** WARNING: Inconsistences found, profile may be wrong. Check the target!", 0, 'c');
	}
    }
#endif

    /* WP used for the XYZ->CIELAB conversion for the regression */

    if (sys->RegWhitePoint.Y <= 0.0)
    {
	double Ymax = 0.0;

	for (i = 0; i < sys->hdr.m.nPatches; i++)
	    if (sys->hdr.m.Allowed[i] && sys->hdr.m.Patches[i].XYZ.Y > Ymax)
		Ymax = sys->hdr.m.Patches[i].XYZ.Y;

	sys->RegWhitePoint = sys->hdr.m.Illuminant;

	/* scale to brightest patch */
	if (Ymax > 0.0 && sys->RegWhitePoint.Y > 0.0) {
	    sys->RegWhitePoint.X *= Ymax / sys->RegWhitePoint.Y;
	    sys->RegWhitePoint.Z *= Ymax / sys->RegWhitePoint.Y;
	    sys->RegWhitePoint.Y *= Ymax / sys->RegWhitePoint.Y;
	}
    }

    weight_sum = 0;

    for (i = n = 0; i < sys->hdr.m.nPatches; i++)
    {
        if (sys->hdr.m.Allowed[i])
        {
	    double C, a, b, weight = 1.0;
	    cmsCIELab Lab;

	    names[n] = sys->hdr.m.Patches[i].Name;

	    for (j = 0; j < 3; j++)
	        data[n].p[j] = sys->hdr.m.Patches[i].Colorant.RGB[j];

	    /*
	    data[n].v[0] = sys->hdr.m.Patches[i].Lab.L;
	    data[n].v[1] = a = sys->hdr.m.Patches[i].Lab.a;
	    data[n].v[2] = b = sys->hdr.m.Patches[i].Lab.b;
	    */

	    cmsXYZ2Lab(&sys->RegWhitePoint, &Lab, &sys->hdr.m.Patches[i].XYZ);
	    data[n].v[0] = Lab.L;
	    data[n].v[1] = a = Lab.a;
	    data[n].v[2] = b = Lab.b;

	    /*
	     * higher weight for neutral colors, rough approximation
	     * of CIE94 weighting, notice that the weights are sqared,
	     * since fit_rspl_w expects weights for the squared error
	     */

	    if (sys->lOverweightNeutrals)
	    {
		C = sqrt(a * a + b * b);
		weight = (1.0 +
			  1.0 / pow(1.0+0.048*C, 2) +
			  1.0 / pow(1.0+0.014*C, 2)) / 3.0;
	    }
	    else {
	    	weight = 1.0;
	    }

	    data[n].w = weight;
	    weight_sum += weight;

	    /* printf("%d: C=%g weight=%g,%g\n",
	    	      i, C, weight, sqrt(weight)); */

	    /* printf("%f %f %f -> %f %f %f\n",
		    data[n].p[0], data[n].p[1], data[n].p[2],
                    data[n].v[0], data[n].v[1], data[n].v[2]); */

	    n++;
	}
    }

    /* normalize weights, such that mean = 1.0 */

    for (i = 0; i < n; i++) {
	data[i].w *= n / weight_sum;
	/* printf("weight(%s)=%g\n", names[i], data[i].w); */
    }

    /*
     * Nail down RGB=0 to Lab=0.
     * At present I'm doing it, but I'm not sure, whether
     * it is a good idea. There seem to be minor issues
     * with the 1D curves as well - and this needs to be
     * considered in conjunction. I'll analyze, when I find
     * some time.
     */

#if 0
    for (i = 0; i < 10; i++)
    {
        for (j = 0; j < 3; j++)
	    data[n].p[j] = data[n].v[j] = 0.0;
        n++;
    }
#endif

    gres[0] = gres[1] = gres[2] = sys->hdr.CLUTPoints;

    spline = new_rspl(3, 3);

    spline->fit_rspl_w(
		spline,
		0, //RSPL_VERBOSE,	// flags
		data,		// data points
		n,		// # of data points
		low,		// lower bounds of RGB range
		high,		// upper bounds of RGB range
		gres,		// grid resolution
		vlow,		// vlow
		vhigh,		// vhigh
		smoothness,	// smooth
		avgDev, 		// avgdev
                NULL
	    );

    /* terminate the row of stars printed by fit_rspl() */
    printf("\n");

    if (sys->lUseNoiseWeights)
    {
	/* compute noise weights */

	weight_sum = 0.0;

	for (i = n = 0; i < sys->hdr.m.nPatches; i++)
	{
	    if (sys->hdr.m.Allowed[i])
	    {
		double MSE;
		cmsCIELab Noise;
		ComputeNoiseRGB2Lab(spline, &data[n],
				    sys->hdr.m.Patches[i].Stdev.RGB,
				    &Noise);
		printf("Noise %s: scan=[%g %g %g] target=[%g %g %g]\n",
		       names[n],
		       Noise.L, Noise.a, Noise.b,
		       sys->hdr.m.Patches[i].StdLab.L,
		       sys->hdr.m.Patches[i].StdLab.a,
		       sys->hdr.m.Patches[i].StdLab.b);

		MSE = pow(sys->SystematicError, 2);
		if (MSE < 0.01) MSE = 0.01;	/* enforce reasonable minimum */

		MSE += pow(Noise.L, 2);
		MSE += pow(Noise.a, 2);
		MSE += pow(Noise.b, 2);
		MSE += pow(sys->hdr.m.Patches[i].StdLab.L, 2);
		MSE += pow(sys->hdr.m.Patches[i].StdLab.a, 2);
		MSE += pow(sys->hdr.m.Patches[i].StdLab.b, 2);

		data[n].w /= MSE;
		weight_sum += data[n].w;

		n++;
	    }
	}

	/* normalize weights, such that mean = 1.0 */

	for (i = 0; i < n; i++) {
	    data[i].w *= n / weight_sum;
	    /* printf("weight(%s)=%g\n", names[i], data[i].w); */
	}

	/* do the regression again, now with the weights */

	spline->fit_rspl_w(
		    spline,
		    0, //RSPL_VERBOSE,	// flags
		    data,		// data points
		    n,		// # of data points
		    low,		// lower bounds of RGB range
		    high,		// upper bounds of RGB range
		    gres,		// grid resolution
		    vlow,		// vlow
		    vhigh,		// vhigh
		    smoothness,		// smooth
                    avgDev, 		// avgdev
                    NULL
		);
    }

    /*
     * Compute the randomized GCV estimate, based on a
     * stochastic estimator of the trace of the influence
     * operator of the regression model.
     *
     * This is based on
     * Wahba, G., Johnson, D. R., Gao, F. and Gong, J. "Adaptive tuning
     * of numerical weather prediction models: Part I: randomized GCV and
     * related methods in three and four dimensional data assimilation."
     * TR 920, April 1994.
     *
     * If lOptRGCV==TRUE, then also estimate the "best"
     * smoothness, by trying several values for the smoothness
     * factor, and choose the one which minimizes the RGCV.
     */

    {
	int i, j;
	int sm;				/* smoothness step */
	int smlow = 0, smhigh = 0;	/* range to try, 0..0 if we don't optimize */
	int up = 0;

	double best_gcv_all = 1e9;
	double best_gcv[3] = {1e9, 1e9, 1e9};
	double best_smoothness_all = 0.0;
	double best_smoothness[3] = {0.0, 0.0, 0.0};
	int best_sm_all = 0;
	int best_sm[3] = {0, 0, 0};

	sys->hdr.cmsprintf("<b>Computing Randomized General Cross Validation ...</b>", 0, 'c');

	if (sys->lOptRGCV){
	    /* try 13 smoothness steps, from -6..6 */
	    smlow = -6; smhigh = 6;
	    printf("Will estimate optimal smoothness factor ...\n");
	    sys->hdr.cmsprintf("Will estimate optimal smoothness factor ...", 0, 'c');
	}

	/* notice, loop is executed only once for lOptRGCV==FALSE */

	for (sm = smhigh; sm >= smlow && up < 2; sm--)
	{
	    double tr[3]  = {0.0, 0.0, 0.0};	/* estimated trace of influence matrix */
	    double rss[3] = {0.0, 0.0, 0.0};	/* residual sum of squares */
	    double gcv[3] = {0.0, 0.0, 0.0};	/* RGCV estimate for the 3 output dimensions */
	    double gcvsum = 0;			/* overall RGCV */

	    static int nrvec = 0;		/* # of elements in rvec */
	    static double *rvec[3];		/* random vector for perturbing data */

	    if (sys->lOptRGCV)
	    {
		/* use an emprically determined step size of sqrt(10) */
		smoothness = pow(10.0, sm/2.0);

		spline->del(spline);
		spline = new_rspl(3, 3);

		printf("Trying smoothness %g\n", smoothness);

		/* fit the data using the current trial smoothness */

		spline->fit_rspl_w(
			    spline,
			    0, //RSPL_VERBOSE,	// flags
			    data,		// data points
			    n,			// # of data points
			    low,		// lower bounds of RGB range
			    high,		// upper bounds of RGB range
			    gres,		// grid resolution
			    vlow,		// vlow
			    vhigh,		// vhigh
			    smoothness,		// smooth
			    avgDev,		// avgdev
                            NULL
			);

		/* terminate the row of stars printed by fit_rspl() */
		/* printf("\n"); */
	    }

	    /*
	     * Create random vector, notice that the same vector is
	     * used subsequently, in order that the results ae better
	     * comparable. Grow vector if necessary.
	     */

	    if (nrvec == 0) {
		/* initial allocation */
		for (j = 0; j < 3; j++) {
		    rvec[j] = malloc(n*sizeof(double));
		}
	    }
	    else if (nrvec < n) {
		/* grow */
		for (j = 0; j < 3; j++) {
		    rvec[j] = realloc(rvec[j], n*sizeof(double));
		}
	    }
	    while (nrvec < n) {
	    	/* fill allocated/growed space */
		for (j = 0; j < 3; j++) {
		    rvec[j][nrvec] = (cmsxRandInt() & 0x80000000) ?
				     RVEC_STD : -RVEC_STD;
		}
		nrvec++;
	    }

	    /* perturb training set with random vector */

	    for (i = 0; i < n; i++) {
		train[i] = data[i];
		for (j = 0; j < 3; j++) {
		    train[i].v[j] += rvec[j][i];
		}
	    }

	    /* fit the perturbed data */

	    cv_spline = new_rspl(3, 3);

	    cv_spline->fit_rspl_w(
			cv_spline,
			0, //RSPL_VERBOSE,	// flags
			train,		// data points
			n,		// # of data points
			low,		// lower bounds of RGB range
			high,		// upper bounds of RGB range
			gres,		// grid resolution
			vlow,		// vlow
			vhigh,		// vhigh
			smoothness,	// smooth
			avgDev, 		// avgdev
                        NULL
		    );

	    /* terminate the row of stars printed by fit_rspl() */
	    /* printf("\n"); */

	    /* compute randomized trace estimate and RSS */

	    for (i = 0; i < n; i++)
	    {
		co point1, point2;

		point1 = point2 = cow2co(data[i]);
		spline->interp(spline, &point1);
		cv_spline->interp(cv_spline, &point2);

		for (j = 0; j < 3; j++) {
		    tr[j] += (1.0/RVEC_VAR) * rvec[j][i] *
			     (rvec[j][i] - (point2.v[j] - point1.v[j]));
		    rss[j] += sqr(point1.v[j] - data[i].v[j]);
		}
	    }

	    for (j = 0; j < 3; j++) {
		printf("RGCV effective number of parameters [%d] = %g\n", j, n-tr[j]);
	    }
	    for (j = 0; j < 3; j++) {
		gcvsum += gcv[j] = rss[j] * n / sqr(tr[j]);
		printf("RGCV [%d]: var=%g, std=%g\n", j, gcv[j], sqrt(gcv[j]));
	    }
	    printf("RGCV: var=%g, std=%g\n", gcvsum, sqrt(gcvsum));
	    printf("smoothness = %g\n", smoothness);


	    if (sys->lOptRGCV)
	    {
		sys->hdr.cmsprintf("smoothness=%1 => RGCV=%2 [%3 %4 %5]",
				   5, 'f', (double)sm, sqrt(gcvsum),
				   sqrt(gcv[0]), sqrt(gcv[1]), sqrt(gcv[2]));
		up++;
		if (gcvsum < best_gcv_all) {
		    best_gcv_all = gcvsum;
		    best_smoothness_all = smoothness;
		    best_sm_all = sm;
		    up = 0;
		}
		for (j = 0; j < 3; j++) {
		    if (gcv[j] < best_gcv[j]) {
			best_gcv[j] = gcv[j];
			best_smoothness[j] = smoothness;
			best_sm[j] = sm;
			up = 0;
		    }
		}
		if (sys->hdr.Gauger) {
		    sys->hdr.Gauger("<b>Optimizing smoothness ...</b>",
				    0, smhigh-smlow+1, smhigh-sm+1);
		}
	    }
	    else {
		sys->hdr.cmsprintf("<b>RGCV:</b> estimated RMS dE=%1", 1, 'f', sqrt(gcvsum));
		sys->hdr.cmsprintf("<b>RGCV:</b> effective number of parameters = [%1 %2 %3]",
				    3, 'f', n-tr[0], n-tr[1], n-tr[2]);
	    }

	    cv_spline->del(cv_spline);
	}

	if (sys->lOptRGCV)
	{
	    if (sys->hdr.Gauger) {
	    	/* signal 100% done */
		sys->hdr.Gauger("<b>Optimizing smoothness ...</b>", 0, 1, 1);
	    }
	    printf("Best smoothness [L*,a*,b*] = [%d %d %d], overall = %d\n",
	    	   best_sm[0], best_sm[1], best_sm[2], best_sm_all);
	    printf("RGCV at best smoothness [L*,a*,b*] = [%g %g %g], overall = %g\n",
	    	   sqrt(best_gcv[0]), sqrt(best_gcv[1]), sqrt(best_gcv[2]), sqrt(best_gcv_all));

	    sys->hdr.cmsprintf("<b>RGCV at best smoothness:</b> %1", 1, 'f', sqrt(best_gcv_all));
	    sys->hdr.cmsprintf("<b>Best smoothness:</b> %1", 1, 'd', best_sm_all);
	    sys->hdr.cmsprintf("Adding user supplied offset: %1", 1, 'd', sys->RsplSmoothness);
	    sys->hdr.cmsprintf("Using smoothness: %1", 1, 'd', best_sm_all+sys->RsplSmoothness);

	    /* account for user supplied smoothness offset */
	    smoothness = best_smoothness_all * pow(10.0, sys->RsplSmoothness/2.0);

	    spline->del(spline);
	    spline = new_rspl(3, 3);

	    printf("Fitting data again, using smoothness %g\n", smoothness);

	    spline->fit_rspl_w(
			spline,
			0, //RSPL_VERBOSE,	// flags
			data,		// data points
			n,		// # of data points
			low,		// lower bounds of RGB range
			high,		// upper bounds of RGB range
			gres,		// grid resolution
			vlow,		// vlow
			vhigh,		// vhigh
			smoothness,	// smooth
			avgDev, 		// avgdev
                        NULL
		    );

	    /* terminate the row of stars printed by fit_rspl() */
	    printf("\n");
	}
    }

    /* now verify the goodness of fit */

    sumdE = sumdE94 = sumdEsq = sumdE94sq = 0;

    for (i = n = 0; i < sys->hdr.m.nPatches; i++)
    {
        if (sys->hdr.m.Allowed[i])
        {
	    double err;
	    cmsCIELab Lab, PatchLab;

	    cmsXYZ2Lab(&sys->RegWhitePoint, &PatchLab, &sys->hdr.m.Patches[i].XYZ);

	    point = cow2co(data[n]);
            spline->interp(spline, &point);
	    Lab.L = point.v[0]; Lab.a = point.v[1]; Lab.b = point.v[2];
	    if (Lab.L < 0.0) Lab.L = 0.0;

	    /* dE[n] = err = cmsDeltaE(&Lab, &sys->hdr.m.Patches[i].Lab); */
	    dE[n] = err = cmsDeltaE(&Lab, &PatchLab);
	    sumdEsq += err * err;
	    sumdE += err;

	    /* dE94[n] = err = cmsCIE94DeltaE(&Lab, &sys->hdr.m.Patches[i].Lab); */
	    dE94[n] = err = cmsCIE94DeltaE(&Lab, &PatchLab);
	    sumdE94sq += err * err;
	    sumdE94 += err;

	    n++;
	}
    }

    cmsxDsort(dE, n);
    cmsxDsort(dE94, n);

    /* 95% quantile */
    n95 = (int) floor(0.95 * (n - 1e-6));

    cmsxMad(dE, n, &med, &mad);
    printf("Fitting error (delta E CIELAB): mean=%g, RMS=%g, 95%%<=%g, median=%g, MAD=%g\n",
    	   sumdE/n, sqrt(sumdEsq/n), dE[n95], med, mad);

    sys->hdr.cmsprintf("Fitting error (delta E CIELAB): mean=%1, RMS=%2, 95%%=%3, median=%4, MAD=%5", 5, 'f', sumdE/n, sqrt(sumdEsq/n), dE[n95], med, mad);

    cmsxMad(dE94, n, &med, &mad);
    printf("Fitting error (delta E CIE94): mean=%g, RMS=%g, 95%%<=%g, median=%g, MAD=%g\n",
    	   sumdE94/n, sqrt(sumdE94sq/n), dE94[n95], med, mad);

    sys->hdr.cmsprintf("Fitting error (delta E CIE94): mean=%1, RMS=%2, 95%%=%3, median=%4, MAD=%5", 5, 'f', sumdE94/n, sqrt(sumdE94sq/n), dE94[n95], med, mad);

    /* very heuristic check */

    if (sumdE/n >= 15)
    {
	sys->hdr.cmsprintf("*** WARNING: Inconsistences found, profile may be wrong. Check the target!", 0, 'c');
    }

    /*********** perform cross validation ***********/

    /* 2-fold cross validation */

    if (sys->l2FoldCV)
    {
	int iter;
	double mse2fold = 0.0;
	double mse2fold_94 = 0.0;
	double mse632x, mse632x_94;

	sys->hdr.cmsprintf("<b>Performing 2-fold cross validation ...</b>", 0, 'c');

	for (iter = 0; iter < 2; iter++)
	{
	    double mse = 0.0;
	    double mse_94 = 0.0;
	    int ntrain, ntest;

	    for (i = 0; i < n; i++)
	    	test[i] = TRUE;

	    /* split into training and test set */

	    for (i = ntrain = 0; i < n; i++) {
	    	if (i % 2 != iter) {
		    train[ntrain++] = data[i];
		    test[i] = FALSE;
		}
	    }

	    cv_spline = new_rspl(3, 3);

	    cv_spline->fit_rspl_w(
			cv_spline,
			0, //RSPL_VERBOSE,	// flags
			train,		// data points
			ntrain,		// # of data points
			low,		// lower bounds of RGB range
			high,		// upper bounds of RGB range
			gres,		// grid resolution
			vlow,		// vlow
			vhigh,		// vhigh
			smoothness,	// smooth
			avgDev, 		// avgdev
                        NULL
		    );

	    for (i = ntest = 0; i < n; i++)
	    {
	    	if (test[i])
		{
		    double err;
		    cmsCIELab Lab1, Lab2;
		    point = cow2co(data[i]);
		    cv_spline->interp(cv_spline, &point);

		    if (point.v[0] < 0) point.v[0] = 0;
		    Lab1.L = point.v[0]; Lab1.a = point.v[1]; Lab1.b = point.v[2];
		    Lab2.L = data[i].v[0]; Lab2.a = data[i].v[1]; Lab2.b = data[i].v[2];

		    err = cmsDeltaE(&Lab1, &Lab2);
		    mse += err * err;

		    err = cmsCIE94DeltaE(&Lab1, &Lab2);
		    mse_94 += err * err;

		    ntest++;
		}
	    }

	    mse /= ntest;
	    mse_94 /= ntest;

	    mse2fold += mse;
	    mse2fold_94 += mse_94;

	    cv_spline->del(cv_spline);

	    if (sys->hdr.Gauger) sys->hdr.Gauger("<b>2-fold CV...</b>", 0, 2, iter+1);
	}

	mse2fold /= 2;
	mse2fold_94 /= 2;

	/* use 2-fold CV mse as rough predictor for e0 bootstrap
	   and compute an adjusted CV analog to 0.632 bootsstrap */

	mse632x = 0.632 * mse2fold + 0.368 * sumdEsq / n;
	mse632x_94 = 0.632 * mse2fold_94 + 0.368 * sumdE94sq / n;

	printf("2-fold CV: estimated RMS dE=%g, dE94=%g\n",
	       sqrt(mse2fold), sqrt(mse2fold_94));
	printf("2-fold adj. CV: estimated RMS dE=%g, dE94=%g\n",
	       sqrt(mse632x), sqrt(mse632x_94));

	sys->hdr.cmsprintf("<b>2-fold CV:</b> estimated RMS dE=%1, dE94=%2",
			   2, 'f', sqrt(mse2fold), sqrt(mse2fold_94));
	sys->hdr.cmsprintf("<b>2-fold adj. CV:</b> estimated RMS dE=%1, dE94=%2",
			   2, 'f', sqrt(mse632x), sqrt(mse632x_94));

	if (sys->hdr.Gauger) sys->hdr.Gauger("Init...", 0, 100, 0);
    }

    /* 10-fold cross validation */

    if (sys->l10FoldCV)
    {
	int iter;
	double mse10fold = 0.0;
	double mse10fold_94 = 0.0;

	sys->hdr.cmsprintf("<b>Performing 10-fold cross validation ...</b>", 0, 'c');

	for (iter = 0; iter < 10; iter++)
	{
	    double mse = 0.0;
	    double mse_94 = 0.0;
	    int ntrain, ntest;

	    for (i = 0; i < n; i++)
	    	test[i] = TRUE;

	    /* split into training and test set */

	    for (i = ntrain = 0; i < n; i++) {
	    	if (i % 10 != iter) {
		    train[ntrain++] = data[i];
		    test[i] = FALSE;
		}
	    }

	    cv_spline = new_rspl(3, 3);

	    cv_spline->fit_rspl_w(
			cv_spline,
			0, //RSPL_VERBOSE,	// flags
			train,		// data points
			ntrain,		// # of data points
			low,		// lower bounds of RGB range
			high,		// upper bounds of RGB range
			gres,		// grid resolution
			vlow,		// vlow
			vhigh,		// vhigh
			smoothness,	// smooth
			avgDev, 		// avgdev
                        NULL
		    );

	    for (i = ntest = 0; i < n; i++)
	    {
	    	if (test[i])
		{
		    double err;
		    cmsCIELab Lab1, Lab2;
		    point = cow2co(data[i]);
		    cv_spline->interp(cv_spline, &point);

		    if (point.v[0] < 0) point.v[0] = 0;
		    Lab1.L = point.v[0]; Lab1.a = point.v[1]; Lab1.b = point.v[2];
		    Lab2.L = data[i].v[0]; Lab2.a = data[i].v[1]; Lab2.b = data[i].v[2];

		    err = cmsDeltaE(&Lab1, &Lab2);
		    mse += err * err;
		    /*
		    printf("\t %g  %g %g %g  %g %g %g\n",
			err, Lab1.L, Lab1.a, Lab1.b, Lab2.L, Lab2.a, Lab2.b);
		    */

		    err = cmsCIE94DeltaE(&Lab1, &Lab2);
		    mse_94 += err * err;

		    ntest++;
		}
	    }

	    mse /= ntest;
	    mse_94 /= ntest;

	    /*
	    printf("  iteration %d: dE=%g dE94=%g\n", iter, sqrt(mse), sqrt(mse_94));
	    */

	    mse10fold += mse;
	    mse10fold_94 += mse_94;

	    cv_spline->del(cv_spline);

	    if (sys->hdr.Gauger) sys->hdr.Gauger("10-fold CV...", 0, 10, iter+1);
	}

	mse10fold /= 10;
	mse10fold_94 /= 10;

	printf("10-fold CV: estimated RMS dE=%g, dE94=%g\n",
	       sqrt(mse10fold), sqrt(mse10fold_94));

	sys->hdr.cmsprintf("<b>10-fold CV:</b> estimated RMS dE=%1, dE94=%2",
			   2, 'f', sqrt(mse10fold), sqrt(mse10fold_94));

	if (sys->hdr.Gauger) sys->hdr.Gauger("Init...", 0, 100, 0);
    }

    /* Leave-one-out cross validation */

    if (sys->lLooCV)
    {
	int iter;
	double mseloo = 0.0;
	double mseloo_94 = 0.0;

	sys->hdr.cmsprintf("<b>Performing LOO cross validation ...</b>", 0, 'c');

	for (iter = 0; iter < n; iter++)
	{
	    /* compute iter-th data set */

	    for (i = j = 0; i < n; i++) {
	    	if (i != iter) {
		    train[j++] = data[i];
		}
	    }

	    /* compute regression */

	    cv_spline = new_rspl(3, 3);

	    cv_spline->fit_rspl_w(
			cv_spline,
			0, //RSPL_VERBOSE,	// flags
			train,		// data points
			n-1,		// # of data points
			low,		// lower bounds of RGB range
			high,		// upper bounds of RGB range
			gres,		// grid resolution
			vlow,		// vlow
			vhigh,		// vhigh
			smoothness,	// smooth
			avgDev, 		// avgdev
                        NULL
		    );

	    /* compute error for left out point */

	    {
		double err;
		cmsCIELab Lab1, Lab2;

		point = cow2co(data[iter]);
		cv_spline->interp(cv_spline, &point);

		if (point.v[0] < 0) point.v[0] = 0;
		Lab1.L = point.v[0]; Lab1.a = point.v[1]; Lab1.b = point.v[2];
		Lab2.L = data[iter].v[0]; Lab2.a = data[iter].v[1]; Lab2.b = data[iter].v[2];

		err = cmsDeltaE(&Lab1, &Lab2);
		mseloo += err * err;
		/* printf("  data point %d: dE=%g\n", err); */

		err = cmsCIE94DeltaE(&Lab1, &Lab2);
		mseloo_94 += err * err;

		if (sys->hdr.Gauger) sys->hdr.Gauger("LOO CV...", 0, n-1, iter+1);
	    }

	    cv_spline->del(cv_spline);
	}

	mseloo /= n;
	mseloo_94 /= n;

	printf("LOO CV: estimated RMS dE=%g, dE94=%g\n",
	       sqrt(mseloo), sqrt(mseloo_94));

	sys->hdr.cmsprintf("<b>LOO CV:</b> estimated RMS dE=%1, dE94=%2",
			   2, 'f', sqrt(mseloo), sqrt(mseloo_94));

	if (sys->hdr.Gauger) sys->hdr.Gauger("Init...", 0, 100, 0);
    }

    /* Bootstrap cross validation */

    if (sys->lBootstrapCV)
    {
	double msee0 = 0.0;
	double mse632 = 0.0;
	double msee0_94 = 0.0;
	double mse632_94 = 0.0;
	int iter, B = sys->BootstrapCVIterations;

	sys->hdr.cmsprintf("<b>Performing bootstrap cross validation ...</b>", 0, 'c');

	for (iter = 0; iter < B; iter++)
	{
	    int ntest;
	    double msee0i = 0.0;
	    double mseemp = 0.0;
	    double msee0i_94 = 0.0;
	    double mseemp_94 = 0.0;

	    for (i = 0; i < n; i++)
	    	test[i] = TRUE;

	    /* resample the data set (with replacement) */

	    for (i = 0; i < n; i++) {
		int idx = cmsxRandInt() % n;
		train[i] = data[idx];
		test[idx] = FALSE;
		/* add some noise
		train[i].v[0] += cmsxRandn() * 1.0;
		train[i].v[1] += cmsxRandn() * 1.0;
		train[i].v[2] += cmsxRandn() * 1.0;
		*/
	    }

	    cv_spline = new_rspl(3, 3);

	    cv_spline->fit_rspl_w(
			cv_spline,
			0, //RSPL_VERBOSE,	// flags
			train,		// data points
			n,		// # of data points
			low,		// lower bounds of RGB range
			high,		// upper bounds of RGB range
			gres,		// grid resolution
			vlow,		// vlow
			vhigh,		// vhigh
			smoothness,	// smooth
			avgDev, 		// avgdev
                        NULL
		    );

	    for (i = ntest = 0; i < n; i++)
	    {
		double err;
		cmsCIELab Lab1, Lab2;

		point = cow2co(train[i]);
		cv_spline->interp(cv_spline, &point);

		if (point.v[0] < 0) point.v[0] = 0;
		Lab1.L = point.v[0]; Lab1.a = point.v[1]; Lab1.b = point.v[2];
		Lab2.L = train[i].v[0]; Lab2.a = train[i].v[1]; Lab2.b = train[i].v[2];

		err = cmsDeltaE(&Lab1, &Lab2);
		mseemp += err * err;

		err = cmsCIE94DeltaE(&Lab1, &Lab2);
		mseemp_94 += err * err;

	    	if (test[i])
		{
		    point = cow2co(data[i]);
		    cv_spline->interp(cv_spline, &point);

		    if (point.v[0] < 0) point.v[0] = 0;
		    Lab1.L = point.v[0]; Lab1.a = point.v[1]; Lab1.b = point.v[2];
		    Lab2.L = data[i].v[0]; Lab2.a = data[i].v[1]; Lab2.b = data[i].v[2];

		    err = cmsDeltaE(&Lab1, &Lab2);
		    msee0i += err * err;

		    err = cmsCIE94DeltaE(&Lab1, &Lab2);
		    msee0i_94 += err * err;

		    ntest++;
		}
	    }

	    mseemp    /= n;
	    mseemp_94 /= n;
	    msee0i    /= ntest;
	    msee0i_94 /= ntest;

#ifdef ALWAYS
	    mseemp = sumdEsq / n;
	    mseemp_94 = sumdE94sq / n;
#endif

	    /*
	    printf("  iteration %d: e0: dE=%g de94=%g emp: dE=%g de94=%g\n",
		   iter, sqrt(msee0i), sqrt(msee0i_94),
		   sqrt(mseemp), sqrt(mseemp_94));
	    */

	    msee0     += msee0i;
	    msee0_94  += msee0i_94;
	    mse632    += 0.632 * msee0i + 0.368 * mseemp;
	    mse632_94 += 0.632 * msee0i_94 + 0.368 * mseemp_94;

	    cv_spline->del(cv_spline);

	    if (sys->hdr.Gauger) sys->hdr.Gauger("bootstrap...", 0, B, iter+1);
	}

	msee0     /= B;
	mse632    /= B;
	msee0_94  /= B;
	mse632_94 /= B;

	printf("e0 bootstrap: estimated RMS dE=%g, dE94=%g\n",
	       sqrt(msee0), sqrt(msee0_94));
	printf("0.632 bootstrap: estimated RMS dE=%g, dE94=%g\n",
	       sqrt(mse632), sqrt(mse632_94));

	sys->hdr.cmsprintf("<b>e0 bootstrap:</b> estimated RMS dE=%1, dE94=%2",
			   2, 'f', sqrt(msee0), sqrt(msee0_94));
	sys->hdr.cmsprintf("<b>0.632 bootstrap:</b> estimated RMS dE=%1, dE94=%2",
			   2, 'f', sqrt(mse632), sqrt(mse632_94));

	if (sys->hdr.Gauger) sys->hdr.Gauger("Init...", 0, 100, 0);
    }

    sys->Rspl = (void *) spline;

    return;
}

static void
CleanupGlobalRegression(LPPROFILERDATA sys)
{
    if (sys->HiTerms)
        MATNfree(sys -> HiTerms);

    if (sys->LoTerms)
        MATNfree(sys -> LoTerms);

    if (sys->Rspl)
	sys->Rspl->del(sys->Rspl);

    sys->HiTerms = NULL;
    sys->LoTerms = NULL;
    sys->Rspl = NULL;
}

/* Fill struct with default values */

BOOL cmsxScannerProfilerInit(LPPROFILERDATA sys)
{
    if (sys == NULL) return FALSE;
    ZeroMemory(sys, sizeof(PROFILERDATA));

    /* set default */
    sys->hdr.cmsprintf = printfDummy;

    sys->hdr.DeviceClass = icSigInputClass;
    sys->hdr.ColorSpace  = icSigRgbData;
    sys->hdr.PCSType     = PT_Lab;
    sys->hdr.Medium      = MEDIUM_REFLECTIVE_D50;

    /* Parameters for white point location */

    memset(&sys->hdr.WhitePointParams, 0, sizeof(sys->hdr.WhitePointParams));

    /* Default values for generation */

    sys->hdr.lUseCIECAM02 = FALSE;
    sys->hdr.CLUTPoints = 16;

    sys->hdr.viewingConditionsPredefDevice=0;
    sys->hdr.viewingConditionsPredefPCS=0;

    /* Default viewing conditions for scanning a practical reflective print */

    sys->hdr.device.La = 32;
    sys->hdr.device.Yb = 20;
    sys->hdr.device.surround = AVG_SURROUND;
    sys->hdr.device.D_value  = 1.0;		/* Complete adaptation */

    /* Viewing conditions of PCS */
    cmsxInitPCSViewingConditions(&sys -> hdr);

    sys -> HiTerms = NULL;
    sys -> LoTerms = NULL;

    strcpy(sys -> hdr.Description,  "no description - Many applications need this to be set to a meaningful value.  Please enter a desciption that will identify this profile.");
    strcpy(sys -> hdr.Manufacturer, "little cms profiler construction set");
    strcpy(sys -> hdr.Copyright,   "No copyright, use freely");
    strcpy(sys -> hdr.Model,       "(unknown)");
    strcpy(sys->hdr.Comments, "Your comment");

    sys->Rspl = NULL;

    /* regression parameters */
    sys->RsplSmoothness = 0;
    // sys->AvgDev = 0.005;
    sys->lOverweightNeutrals = TRUE;
    sys->lUseNoiseWeights = FALSE;
    sys->SystematicError = 0.5;

    sys->lOptRGCV = FALSE;
    sys->lLooCV = FALSE;
    sys->l2FoldCV = FALSE;
    sys->l10FoldCV = FALSE;
    sys->lBootstrapCV = FALSE;
    sys->BootstrapCVIterations = 100;

    sys->hdr.ProfileVerbosityLevel = 0;

    return TRUE;
}


/* Auxiliar: take RGB and update gauge */

static
void GetRGB(LPPROFILERCOMMONDATA hdr, register WORD In[], double* r, double* g, double* b)
{
    static int Count = 0, n_old = 0;
    double R, G, B;
    int n;


    R = _cmsxSaturate65535To255(In[0]);       /* Convert from the sheet notation */
    G = _cmsxSaturate65535To255(In[1]);       /* 0..255.0, to our notation */
    B = _cmsxSaturate65535To255(In[2]);       /* of 0..0xffff, 0xffff/255 = 257 */

    if (R == 0 && G == 0 && B == 0)
    {
        Count = 0;
        n_old = -1;
    }

    n = (int) (double) (100. * Count)  / (hdr->CLUTPoints * hdr->CLUTPoints * hdr->CLUTPoints);
    Count++;

    if (n > n_old)
    {
        if (hdr->Gauger) hdr->Gauger("", 0, 100, (int) n);
    }

    n_old = n;
    *r = R; *g = G; *b = B;
}


/* The sampler for Lab */

static int
RegressionSamplerLab(register WORD In[], register WORD Out[], register LPVOID Cargo)
{
    cmsCIEXYZ xyz;
    cmsCIELab Lab;
    double r, g, b;
    LPPROFILERDATA sys = (LPPROFILERDATA) Cargo;
    char code;
    co data;
    rspl *spline;

    GetRGB(&sys->hdr, In, &r, &g, &b);

    spline = (rspl *) sys->Rspl;

    data.p[0] = r; data.p[1] = g; data.p[2] = b;
    spline->interp(spline, &data);
    Lab.L = data.v[0]; Lab.a = data.v[1]; Lab.b = data.v[2];

    /* Regression CAN deliver wrong values. Clamp these. */
    /* Don't clip Lightness */
    /* cmsClampLab(&Lab, 127.9961, -128, 127.9961, -128); */
    if (Lab.L <    0) Lab.L =    0;
    if (Lab.a < -127) Lab.a = -127;
    if (Lab.a >  127) Lab.a =  127;
    if (Lab.b < -127) Lab.b = -127;
    if (Lab.b >  127) Lab.b =  127;

    /* Normalize */

    cmsLab2XYZ(&sys->RegWhitePoint, &xyz, &Lab);
    xyz.X *= 0.01; xyz.Y *= 0.01; xyz.Z *= 0.01;
    cmsxChromaticAdaptationAndNormalization(&sys->hdr, &xyz, FALSE);
    cmsXYZ2Lab(cmsD50_XYZ(), &Lab, &xyz);

    /* Clamping again, adaptation could move slightly values */
    /* cmsClampLab(&Lab, 127.9961, -128, 127.9961, -128); */
    if (Lab.L <    0) Lab.L = 0;
    if (Lab.L >  100) Lab.L =  100;
    if (Lab.a < -127) Lab.a = -127;
    if (Lab.a >  127) Lab.a =  127;
    if (Lab.b < -127) Lab.b = -127;
    if (Lab.b >  127) Lab.b =  127;

    /* To PCS encoding */
    cmsFloat2LabEncoded(Out, &Lab);

    return TRUE; /* And done with success */
}

/* The sampler for XYZ */

static
int RegressionSamplerXYZ(register WORD In[], register WORD Out[], register LPVOID Cargo)
{
    cmsCIEXYZ xyz;
    double r, g, b;
    LPPROFILERDATA sys = (LPPROFILERDATA) Cargo;
    char code;

    GetRGB(&sys->hdr, In, &r, &g, &b);
    code = cmsxHullCheckpoint(sys ->hdr.hRGBHull,
                              (int) floor(r + .5),
                              (int) floor(g + .5),
                              (int) floor(b + .5));

    if (code == 'i')
    { /* Inside gamut */
        if (!cmsxRegressionRGB2XYZ(r, g, b, sys -> HiTerms, &xyz)) return FALSE;
    }
    else
        if (code == 'o')
        {  /* outside gamut */
            if (!cmsxRegressionRGB2XYZ(r, g, b, sys -> LoTerms, &xyz)) return FALSE;
        }
       else
       {	/* At gamut hull boundaries */
           if (!cmsxRegressionInterpolatorRGB(&sys -> hdr.m,
                                           PT_XYZ,
                                           10,
                                           TRUE,
                                           30,
                                           r, g, b,
                                           &xyz)) return FALSE;
    }

    xyz.X /= 100.;
    xyz.Y /= 100.;
    xyz.Z /= 100.;

    cmsxChromaticAdaptationAndNormalization(&sys->hdr, &xyz, FALSE);

    /* To PCS encoding. It also claps bad values */
    cmsFloat2XYZEncoded(Out, &xyz);

    return TRUE; /* And done witch success */
}


/* The main scanner profiler */

BOOL cmsxScannerProfilerDo(LPPROFILERDATA sys)
{

    LPLUT AToB0;
    LPLUT AToB1;
    DWORD dwNeedSamples;

    /* set default */
    if (! sys->hdr.cmsprintf)
	sys->hdr.cmsprintf = printfDummy;

    if (!*sys -> hdr.OutputProfileFile)
        return FALSE;

    /*  WhitePoint durring the call to
        cmsxChoosePCS in file cmsprf.c which calls
           cmsxComputeMatrixShaper in file cmsmkmsh.c
           which calls either
              cmsxPCollFindWhite in file cmspcoll.c
              or
              ComputeWhiteAndBlackPoints in file cmsmkmsh.c
        where the white point is calulated.
    */

    if (!cmsxChoosePCS(&sys->hdr))
        return FALSE;

    dwNeedSamples = PATCH_HAS_RGB | PATCH_HAS_XYZ;
    if (sys ->hdr.PCSType == PT_Lab) dwNeedSamples |= PATCH_HAS_Lab;

    sys->hdr.cmsprintf("Loading sheets...", 0, 'c');
    if (sys->hdr.ReferenceSheet[0])
	sys->hdr.cmsprintf("Reference sheet: %1", 1, 's', sys->hdr.ReferenceSheet);
    if (sys->hdr.MeasurementSheet[0])
	sys->hdr.cmsprintf("Measurement sheet: %1", 1, 's', sys->hdr.MeasurementSheet);

    if (!cmsxPCollBuildMeasurement(&sys->hdr.m,
                                   sys->hdr.ReferenceSheet,
                                   sys->hdr.MeasurementSheet,
                                   dwNeedSamples)) return FALSE;

    sys->hdr.hProfile = cmsCreateRGBProfile(NULL, NULL, NULL);

    cmsSetDeviceClass(sys->hdr.hProfile, sys->hdr.DeviceClass);
    cmsSetColorSpace(sys->hdr.hProfile,  sys->hdr.ColorSpace);
    cmsSetPCS(sys->hdr. hProfile, _cmsICCcolorSpace(sys->hdr.PCSType));

    /* Save char target tag */
    if (sys->hdr.ProfileVerbosityLevel >= 2)
    {
        /* this is blowing up for me 
           is this happenig to anyone else> */
        cmsxEmbedCharTarget(&sys -> hdr);
    }

    AToB0 = cmsAllocLUT();
    AToB1 = cmsAllocLUT();

    cmsAlloc3DGrid(AToB0, sys->hdr.CLUTPoints, 3, 3);
    cmsAlloc3DGrid(AToB1, sys->hdr.CLUTPoints, 3, 3);

    cmsxComputeLinearizationTables(&sys-> hdr.m,
                                   sys -> hdr.PCSType,
                                   sys -> Prelinearization,
                                   1024,
                                   MEDIUM_REFLECTIVE_D50);

    /* Refresh RGB of all patches. This converts all regression into */
    /* near linear RGB->Lab or XYZ */

    cmsxPCollLinearizePatches(&sys->hdr.m, sys->hdr.m.Allowed, sys->Prelinearization);
    cmsxComputeGamutHull(&sys->hdr);

    if (sys->hdr.CLUTPoints > 2)
	ComputeGlobalRegression(sys);

    /* Locate the desired media white point */

    {
        cmsCIEXYZ illuminant = sys->hdr.m.Illuminant;
        LPPATCH DminPatch = cmsxPCollFindDmin(&sys->hdr.m,
                                              sys->hdr.m.Allowed);

        illuminant.X *= 0.01; illuminant.Y *= 0.01; illuminant.Z *= 0.01;

	/* determine XYZ of WP */

	switch (sys->hdr.WhitePointParams.location)
	{
	    case WP_LOC_DMIN:
		if (DminPatch) {
		    /* use color of DMIN patch */
		    sys->hdr.WhitePoint = DminPatch->XYZ;
		    sys->hdr.WhitePoint.X *= 0.01;
		    sys->hdr.WhitePoint.Y *= 0.01;
		    sys->hdr.WhitePoint.Z *= 0.01;
		}
		else {
		    /* fallback */
		    printf("No valid DMIN patch found\n");
		    printf("Using illuminant color as fallback white point\n");
		    sys->hdr.WhitePoint = illuminant;
		}
		break;

	    case WP_LOC_ILLUMINANT:
		sys->hdr.WhitePoint = illuminant;
		break;

	    case WP_LOC_RGB_255:
		/* sys->hdr.WhitePoint already contains extrapolated WP */
		break;

	    case WP_LOC_RGB:
		{
		    int i;
		    co data;
		    cmsCIELab Lab;
		    cmsCIEXYZ xyz;

		    for (i = 0; i < 3; i++)
			data.p[i] = sys->hdr.WhitePointParams.RGB[i];
		    cmsxApplyLinearizationTable(data.p, sys->Prelinearization, data.p);
		    sys->Rspl->interp(sys->Rspl, &data);
		    Lab.L = data.v[0]; Lab.a = data.v[1]; Lab.b = data.v[2];
		    if (Lab.L < 0.0)
		    	Lab.L = Lab.a = Lab.b = 0.0;
		    cmsLab2XYZ(&sys->RegWhitePoint, &sys->hdr.WhitePoint, &Lab);
		    sys->hdr.WhitePoint.X *= 0.01;
		    sys->hdr.WhitePoint.Y *= 0.01;
		    sys->hdr.WhitePoint.Z *= 0.01;
		}
		break;

	    case WP_LOC_XYZ:
	    	sys->hdr.WhitePoint = sys->hdr.WhitePointParams.XYZ;
	    	sys->hdr.WhitePoint.X *= 0.01;
	    	sys->hdr.WhitePoint.Y *= 0.01;
	    	sys->hdr.WhitePoint.Z *= 0.01;
		break;

	    case WP_LOC_LAB:
		cmsLab2XYZ(&illuminant,
			   &sys->hdr.WhitePoint,
			   &sys->hdr.WhitePointParams.Lab);
		break;

	    default:
		printf("Internal error: Unknown white point location %d\n",
		       (int) sys->hdr.WhitePointParams.location);
		printf("Using illuminant color as fallback white point\n");
		sys->hdr.WhitePoint = illuminant;
		break;
	}

	/* compute corresponding RGB numbers, for display purposes only */

	switch (sys->hdr.WhitePointParams.location)
	{
	    case WP_LOC_RGB_255:
		sys->hdr.WhitePointParams.RGB[0] =
		sys->hdr.WhitePointParams.RGB[1] =
		sys->hdr.WhitePointParams.RGB[2] = 255.0;
		break;

	    case WP_LOC_RGB:
		/* RGB numbers already prescribed */
		break;

	    case WP_LOC_DMIN:
	    	if (DminPatch) {
		    int i;
		    for (i = 0; i < 3; i++)
			sys->hdr.WhitePointParams.RGB[i] =
			    DminPatch->Colorant.RGB[i];
		    break;
		}
		/* FALLTHROUGH */

	    default:
		/* XXX FIXME: to be implemented */
		/* should do reverse lookup */
		sys->hdr.WhitePointParams.RGB[0] =
		sys->hdr.WhitePointParams.RGB[1] =
		sys->hdr.WhitePointParams.RGB[2] = 0.0;
	}

	/* fill-in XYZ and CIELAB values */

	sys->hdr.WhitePointParams.XYZ = sys->hdr.WhitePoint;

	cmsXYZ2Lab(&illuminant,
		   &sys->hdr.WhitePointParams.Lab,
		   &sys->hdr.WhitePointParams.XYZ);

        /* restore to the value the user entered */
        sys->hdr.WhitePointParams.XYZ.X *= 100.0;
        sys->hdr.WhitePointParams.XYZ.Y *= 100.0;
        sys->hdr.WhitePointParams.XYZ.Z *= 100.0;

	/* update numbers in the GUI */

	if (sys->hdr.WhitePointParams.updateGui)
	    sys->hdr.WhitePointParams.updateGui(
		sys->hdr.WhitePointParams.updateGuiData);

	printf("Media white point location: %d\n",
	       sys->hdr.WhitePointParams.location);
	printf("    XYZ: %f, %f, %f\n",
	       sys->hdr.WhitePointParams.XYZ.X,
	       sys->hdr.WhitePointParams.XYZ.Y,
	       sys->hdr.WhitePointParams.XYZ.Z);
	printf("    Lab: %f, %f, %f\n",
	       sys->hdr.WhitePointParams.Lab.L,
	       sys->hdr.WhitePointParams.Lab.a,
	       sys->hdr.WhitePointParams.Lab.b);
	printf("    RGB: %f, %f, %f\n",
	       sys->hdr.WhitePointParams.RGB[0],
	       sys->hdr.WhitePointParams.RGB[1],
	       sys->hdr.WhitePointParams.RGB[2]);
    }

/* XXX would this improve the accuracy?

    // do regression again, now using real media WP

    if (sys->hdr.CLUTPoints > 2)
    {
	sys->RegWhitePoint.X = sys->hdr.WhitePoint.X * 100.;
	sys->RegWhitePoint.Y = sys->hdr.WhitePoint.Y * 100.;
	sys->RegWhitePoint.Z = sys->hdr.WhitePoint.Z * 100.;

	CleanupGlobalRegression(sys);
	ComputeGlobalRegression(sys);
    }
*/

    cmsAllocLinearTable(AToB0, sys->Prelinearization, 1);
    cmsAllocLinearTable(AToB1, sys->Prelinearization, 1);

    /* Set CIECAM02 parameters */

    sys->hdr.device.whitePoint.X = sys->hdr.WhitePoint.X * 100.;
    sys->hdr.device.whitePoint.Y = sys->hdr.WhitePoint.Y * 100.;
    sys->hdr.device.whitePoint.Z = sys->hdr.WhitePoint.Z * 100.;

    if (sys->hdr.CLUTPoints > 2)
    {
	sys->hdr.hDevice = cmsCIECAM02Init(&sys->hdr.device);
	sys->hdr.hPCS    = cmsCIECAM02Init(&sys->hdr.PCS);

	if (sys -> hdr.PCSType == PT_Lab) {
	    sys->hdr.lUseCIECAM02 = TRUE;
	    cmsSample3DGrid(AToB0, RegressionSamplerLab, sys, 0);
	    sys->hdr.lUseCIECAM02 = FALSE;
	    cmsSample3DGrid(AToB1, RegressionSamplerLab, sys, 0);
	}
	else {
	    sys->hdr.lUseCIECAM02 = TRUE;
	    cmsSample3DGrid(AToB0, RegressionSamplerXYZ, sys, 0);
	    sys->hdr.lUseCIECAM02 = FALSE;
	    cmsSample3DGrid(AToB1, RegressionSamplerXYZ, sys, 0);
	}

	cmsCIECAM02Done(sys->hdr.hDevice);
	cmsCIECAM02Done(sys->hdr.hPCS);

	cmsAddTag(sys->hdr.hProfile, icSigAToB0Tag, AToB0);
	cmsAddTag(sys->hdr.hProfile, icSigAToB1Tag, AToB1);
	cmsAddTag(sys->hdr.hProfile, icSigAToB2Tag, AToB0);
    }

    cmsxEmbedTextualInfo(&sys -> hdr);

    cmsAddTag(sys->hdr.hProfile, icSigMediaWhitePointTag, &sys->hdr.WhitePoint);

    cmsAddTag(sys->hdr.hProfile, icSigMediaBlackPointTag, &sys->hdr.BlackPoint);

    cmsSetProfileICCversion(sys->hdr.hProfile, 0x2400000);

    /* Save primaries & gamma curves */
    if (sys->hdr.ProfileVerbosityLevel >= 1 || sys->hdr.CLUTPoints <= 2)
    {
        cmsxEmbedMatrixShaper(&sys ->hdr);
    }

    _cmsSaveProfile(sys->hdr.hProfile, sys->hdr.OutputProfileFile);

    cmsCloseProfile(sys->hdr.hProfile);
    sys->hdr.hProfile = NULL;

    cmsxPCollFreeMeasurements(&sys->hdr.m);

    cmsFreeLUT(AToB0);
    cmsFreeLUT(AToB1);

    CleanupGlobalRegression(sys);

    if (sys->Prelinearization[0])
        cmsFreeGammaTriple(sys->Prelinearization);

    if (sys->hdr.Gamma[0])
        cmsFreeGammaTriple(sys->hdr.Gamma);

    return TRUE;
}

