
			

/* 
 * Argyll Color Management System
 * Print Device calibration curve generator.
 *
 * Author: Graeme W. Gill
 * Date:   2008/3/3
 *
 * Copyright 1996-2008 Graeme W. Gill
 * All rights reserved.
 *
 * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
 * see the License.txt file for licencing details.
 */

/*
 * This program takes in the colorent wedge test chart
 * points, and creates a set of per channel correction curves.
 */

/*
 * TTBD:
 *		Allow auto max threshold to be scaled on command line ?
 *		ie. -m# set % to go below the default optimal maximum.
 */


/*
	Additive spaces are handled by inverting the device values internally.
	(Such a space should probably have ICX_INVERTED set as well, indicating
	 that the underlying device is actually subtractive.) 
 */

#undef DEBUG

#define verbo stdout

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include "copyright.h"
#include "aconfig.h"
#include "cgats.h"
#include "numlib.h"
#include "sort.h"
#include "rspl.h"
#include "conv.h"
#include "xicc.h"
#include "plot.h"
#include "vrml.h"
#include "ui.h"

#define RSPLFLAGS (0 /* | RSPL_2PASSSMTH | RSPL_EXTRAFIT2 */)

#define RSPLSMOOTH 1.0	/* RSPL Smoothness factor use on measured device points */

#define TCURVESMOOTH 1.0 /* RSPL smoothness factor for target aim points */

#define GRES 2048		/* Rspl grid resolution */
#define SLOPE_NORM 70.0	/* Normalized delta E for below thresholds */
#define MIN_SLOPE_A 8.0	/* Criteria for Auto max, DE/dDev at max */
#define MIN_SLOPE_O 3.0	/* Criteria for Auto max, min DE/dDev below max */

#define MAX_CAL_RES 2048	/* Maximum resolution saved to .cal file */
#define DEF_CAL_RES 256	/* Default resolution saved to .cal file */

#define PRES 256		/* Plotting & Reporting resolution */

void usage(char *diag, ...) {
	int i;
	fprintf(stderr,"Create printer calibration, Version %s\n",ARGYLL_VERSION_STR);
	fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
	if (diag != NULL) {
		va_list args;
		fprintf(stderr,"  Diagnostic: ");
		va_start(args, diag);
		vfprintf(stderr, diag, args);
		va_end(args);
		fprintf(stderr,"\n");
	}
	fprintf(stderr,"usage: %s [-options] [prevcal] inoutfile\n",error_program);
	fprintf(stderr," -v verbosity    Verbose mode\n");
	fprintf(stderr," -p              Plot graphs.\n");
	fprintf(stderr," -w              Save colorant 3D plot to inoutname_r%s and inoutname_c%s\n",vrml_ext(),vrml_ext());
	fprintf(stderr," -i              Initial calibration, set targets, create .cal\n");
	fprintf(stderr," -r              Re-calibrate against previous .cal and create new .cal\n");
	fprintf(stderr," -e              Verify against previous .cal\n");
	fprintf(stderr," -I              Create imitation target from .ti3 and null calibration\n");
	fprintf(stderr," -d              Go through the motions but don't write any files\n");
	fprintf(stderr," -z res          Resolution of created calibration curves (default %d)\n",DEF_CAL_RES);
	fprintf(stderr," -s smoothing    Extra curve smoothing (default 1.0)\n");
	fprintf(stderr," -A manufacturer Set the manufacturer description string\n");
	fprintf(stderr," -M model        Set the model description string\n");
	fprintf(stderr," -D description  Set the profile Description string\n");
	fprintf(stderr," -C copyright    Set the copyright string\n");
	fprintf(stderr," -x# percent     Set initial maximum device %% target (override auto)\n");
	fprintf(stderr," -m# percent     Set initial dev target to %% of auto maximum\n");
	fprintf(stderr," -n# deltaE      Set initial white minimum deltaE target\n");
	fprintf(stderr," -t# percent     Set initial 50%% transfer curve percentage target\n");
	fprintf(stderr,"   # = c, r, 0	 First channel\n");
	fprintf(stderr,"       m, g, 1	 Second channel\n");
	fprintf(stderr,"       y, b, 2	 Third channel\n");
	fprintf(stderr,"       k,    3	 Fourth channel, etc.\n");
	fprintf(stderr," -a              Create an Adobe Photoshop .AMP file as well as a .cal\n");
	fprintf(stderr," prevcal         Base name of previous .cal file for recal or verify.\n");
	fprintf(stderr," inoutname       Base name of input .ti3 file, output .cal file\n");
	exit(1);
}

/* - - - - - - - - - - - - - - - - - - - - - - - */
typedef struct {
	double loc;					/* Location up the curve 0.0 - 1.0 */
	double val[MAX_CHAN];		/* Value at that location 0.0 - 1.0 */
} trans_point;

/* Class to hold a print calibration target */
struct _pcaltarg {
	inkmask devmask;            	/* ICX ink mask of device space */
	
	/* Note that with all of these, a value < 0.0 */
	/* indicates no value set. */
	int devmaxset;						/* Flag - nz if the devmax is set */
	double devmax[MAX_CHAN];			/* Device value maximum 0.0 - 1.0 */

	int ademaxset;						/* Flag - nz if the ademax is set */
	double ademax[MAX_CHAN];			/* abs DE maximum for each channel */

	int ademinset;						/* Flag - nz if the ademin is set */
	double ademin[MAX_CHAN];			/* abs DE minimum for each channel */

	int no_tpoints;						/* Number of transfer curve points */
	trans_point *tpoints;				/* Array of transfer curve points */

	char err[500];						/* Error message from diagnostics */

	/* Methods */
	void (*del)(struct _pcaltarg *p);

	/* Save/restore to a CGATS file */
	int (*write)(struct _pcaltarg *p, cgats *cg, int tab);	/* return nz on error */
	int (*read)(struct _pcaltarg *p, cgats *cg, int tab);	/* return nz on error */

	/* Set values in the target */
	void (*update_devmax)(struct _pcaltarg *p, int chan, double val);
	void (*update_ademax)(struct _pcaltarg *p, int chan, double val);
	void (*update_ademin)(struct _pcaltarg *p, int chan, double val);
	void (*update_tcurve)(struct _pcaltarg *p, int chan, double loc, double val);

	/* Reurn nz if the target has been set */
	int (*is_set)(struct _pcaltarg *p);

	/* Update settings or from one from another */
	void (*update)(struct _pcaltarg *p, struct _pcaltarg *s);

}; typedef struct _pcaltarg pcaltarg; 

static void pcaltarg_del(pcaltarg *p) {
	if (p != NULL) {
		free(p);
	}
}

/* Write the cal target to a given cgats table */
static int pcaltarg_write(pcaltarg *p, cgats *cg, int tab) {
	int i, j;
	time_t clk = time(0);
	struct tm *tsp = localtime(&clk);
	char *atm = asctime(tsp); /* Ascii time */
	char *ident = icx_inkmask2char(p->devmask, 1); 
	char *bident = icx_inkmask2char(p->devmask, 0); 
	int devchan = icx_noofinks(p->devmask);
	int nsetel = 0;
	cgats_set_elem *setel;	/* Array of set value elements */
	char buf[100];

	atm[strlen(atm)-1] = '\000';	/* Remove \n from end */

	/* Setup output cgats file */
	cg->add_table(cg, tt_other, 0);   /* Add a table for Calibration TarGet values */
	cg->add_kword(cg, tab, "DESCRIPTOR", "Argyll Calibration Aim Target Definition File",NULL);
	cg->add_kword(cg, tab, "ORIGINATOR", "Argyll printcal", NULL);
	cg->add_kword(cg, tab, "CREATED",atm, NULL);
	cg->add_kword(cg, tab, "COLOR_REP", ident, NULL);

	/* Setup the table, which holds all the model parameters. */
	/* There is always a parameter per X Y Z or spectral band */
	cg->add_field(cg, tab, "PARAMTYPE", nqcs_t);
	nsetel++;
	sprintf(buf, "%s_I",bident);
	cg->add_field(cg, tab, buf, r_t);
	nsetel++;
	for (i = 0; i < devchan; i++) {
		inkmask imask = icx_index2ink(p->devmask, i);
		sprintf(buf, "%s_%s",bident,icx_ink2char(imask));
		cg->add_field(cg, tab, buf, r_t);
		nsetel++;
	}

	if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL) {
		free(ident);
		free(bident);
		sprintf(p->err,"ctg_write: malloc of setel failed");
		return 1;
	}

	/* Write out the values */
	if (p->devmaxset) {
		/* This is informational only */
		setel[0].c = "DEVMAX_USED";
		setel[1].d = 0.0;		/* Not used */
			
		if (p->devmask & ICX_ADDITIVE) {
			for (i = 0; i < devchan; i++)
				setel[2+i].d = 1.0 - p->devmax[i];
		} else {
			for (i = 0; i < devchan; i++)
				setel[2+i].d = p->devmax[i];
		}
		cg->add_setarr(cg, tab, setel);
	}
	if (p->ademaxset) {
		setel[0].c = "DELMAX_AIM";
		setel[1].d = 0.0;		/* Not used */
			
		for (i = 0; i < devchan; i++)
			setel[2+i].d = p->ademax[i];
		cg->add_setarr(cg, tab, setel);
	}
	if (p->ademinset) {
		setel[0].c = "DELMIN_AIM";
		setel[1].d = 0.0;		/* Not used */
			
		for (i = 0; i < devchan; i++)
			setel[2+i].d = p->ademin[i];
		cg->add_setarr(cg, tab, setel);
	}
	for (j = 0; j < p->no_tpoints; j++) {
		setel[0].c = "TRANS_PNT";
		setel[1].d = p->tpoints[j].loc;

		for (i = 0; i < devchan; i++)
			setel[2+i].d = p->tpoints[j].val[i];
		cg->add_setarr(cg, tab, setel);
	}

	free(setel);
	free(ident);
	free(bident);

	return 0;
}

/* Read the cal target from a given cgats table */
static int pcaltarg_read(pcaltarg *p, cgats *cg, int tab) {
	char *bident;
	int devchan;
	int i, j, ix;
	int ti;					/* Temporary CGATs index */
	int spi[2+MAX_CHAN];	/* CGATS indexes for each field */
	char buf[100];

	if ((ti = cg->find_kword(cg, tab, "COLOR_REP")) < 0) {
		sprintf(p->err, "ctg_read: Can't fint COLOR_REP");
		return 1;
	}

	if ((p->devmask = icx_char2inkmask(cg->t[tab].kdata[ti]) ) == 0) {
		sprintf(p->err, "ctg_read: unrecognized COLOR_REP '%s'",cg->t[tab].kdata[ti]);
		return 1;
	}
	devchan = icx_noofinks(p->devmask);
	bident = icx_inkmask2char(p->devmask, 0); 

	/* Figure out the indexes of all the fields */
	if ((spi[0] = cg->find_field(cg, tab, "PARAMTYPE")) < 0) {
		sprintf(p->err, "ctg_read: Can't find field PARAMTYPE");
		free(bident);
		return 1;
	}
	sprintf(buf, "%s_I",bident);
	if ((spi[1] = cg->find_field(cg, tab, buf)) < 0) {
		sprintf(p->err, "ctg_read: Can't find field %s",buf);
		free(bident);
		return 1;
	}
	for (i = 0; i < devchan; i++) {
		inkmask imask = icx_index2ink(p->devmask, i);
		sprintf(buf, "%s_%s",bident,icx_ink2char(imask));
		if ((spi[2+i] = cg->find_field(cg, tab, buf)) < 0) {
			sprintf(p->err, "ctg_read: Can't find field %s",buf);
			free(bident);
			return 1;
		}
	}

	/* Go through all the entries in the table, putting them in the right place */
	for (ix = 0; ix < cg->t[tab].nsets; ix++) {

		if (strcmp((char *)cg->t[tab].fdata[ix][spi[0]], "DELMAX_AIM") == 0) { 
			for (i = 0; i < devchan; i++)
				p->ademax[i] = *((double *)cg->t[tab].fdata[ix][spi[2+i]]);
			p->ademaxset = 1;

		} else if (strcmp((char *)cg->t[tab].fdata[ix][spi[0]], "DELMIN_AIM") == 0) { 
			for (i = 0; i < devchan; i++)
				p->ademin[i] = *((double *)cg->t[tab].fdata[ix][spi[2+i]]);
			p->ademinset = 1;

		} else if (strcmp((char *)cg->t[tab].fdata[ix][spi[0]], "TRANS_PNT") == 0) { 
			if ((p->tpoints = (trans_point *)realloc(p->tpoints, sizeof(trans_point)
			                                                   * (p->no_tpoints+1))) == NULL)
				error("Realloc of tpoints");
			p->tpoints[p->no_tpoints].loc = *((double *)cg->t[tab].fdata[ix][spi[1]]);
			for (i = 0; i < devchan; i++)
				p->tpoints[p->no_tpoints].val[i] = *((double *)cg->t[tab].fdata[ix][spi[2+i]]);
			p->no_tpoints++;
		}
	}
	free(bident);

	return 0;
}

/* Update an individual setting. Use chan < 0 to set all to default */
void pcaltarg_update_devmax(struct _pcaltarg *p, int chan, double val) {
	int i;
	if (p->devmaxset == 0) {
		for (i = 0; i < MAX_CHAN; i++)
			p->devmax[i] = -1.0;
		p->devmaxset = 1;
	}
	if (chan >= 0)
		p->devmax[chan] = val;
}
void pcaltarg_update_ademax(struct _pcaltarg *p, int chan, double val) {
	int i;
	if (p->ademaxset == 0) {
		for (i = 0; i < MAX_CHAN; i++)
			p->ademax[i] = -1.0;
		p->ademaxset = 1;
	}
	if (chan >= 0)
		p->ademax[chan] = val;
}
void pcaltarg_update_ademin(struct _pcaltarg *p, int chan, double val) {
	int i;
	if (p->ademinset == 0) {
		for (i = 0; i < MAX_CHAN; i++)
			p->ademin[i] = -1.0;
		p->ademinset = 1;
	}
	if (chan >= 0)
		p->ademin[chan] = val;
}
void pcaltarg_update_tcurve(struct _pcaltarg *p, int chan, double loc, double val) {
	int i, j;

	/* See if a transfer curve point already exists */
	for (j = 0; j < p->no_tpoints; j++) {
		if (p->tpoints[j].loc == loc)
			break;
	}
	/* If not, allocate a new one */
	if (j >= p->no_tpoints) {
		p->no_tpoints++;
		if ((p->tpoints = (trans_point *)realloc(p->tpoints, sizeof(trans_point) * p->no_tpoints)) == NULL)
			error("Realloc of tpoints");
		p->tpoints[j].loc = loc;
		for (i = 0; i < MAX_CHAN; i++)
			p->tpoints[j].val[i] = -1.0;
	}
	p->tpoints[j].val[chan] = val;

	if (p->no_tpoints > 0) {
		/* Sort the transfer points into loc order */
#define HEAP_COMPARE(A,B) ((A).loc < (B).loc)
		HEAPSORT(trans_point, p->tpoints, p->no_tpoints);
#undef HEAP_COMPARE
	}
}

/* Reurn nz if the target has been set */
static int pcaltarg_is_set(pcaltarg *p) {
	if (p->devmaxset != 0
	 || p->ademaxset != 0
	 || p->ademinset != 0
	 || p->no_tpoints > 0)
		return 1;
	return 0;
}

/* Update one from another */
static void pcaltarg_update(pcaltarg *p, pcaltarg *s) {
	int i, j, k;

	if (s->devmaxset) {
		if (p->devmaxset == 0) {
			for (i = 0; i < MAX_CHAN; i++)
				p->devmax[i] = -1.0;
			p->devmaxset = 1;
		}
		for (i = 0; i < MAX_CHAN; i++) {
			if (s->devmax[i] >= 0.0)
				p->devmax[i] = s->devmax[i];
		}
	}

	if (s->ademaxset) {
		if (p->ademaxset == 0) {
			for (i = 0; i < MAX_CHAN; i++)
				p->ademax[i] = -1.0;
			p->ademaxset = 1;
		}
		for (i = 0; i < MAX_CHAN; i++) {
			if (s->ademax[i] >= 0.0)
				p->ademax[i] = s->ademax[i];
		}
	}

	if (s->ademinset) {
		if (p->ademinset == 0) {
			for (i = 0; i < MAX_CHAN; i++)
				p->ademin[i] = -1.0;
			p->ademinset = 1;
		}
		for (i = 0; i < MAX_CHAN; i++) {
			if (s->ademin[i] >= 0.0)
				p->ademin[i] = s->ademin[i];
		}
	}

	/* For each source transfer curve point */
	for (k = 0; k < s->no_tpoints; k++) {

		/* See if a transfer curve point already exists */
		for (j = 0; j < p->no_tpoints; j++) {
			if (p->tpoints[j].loc == s->tpoints[k].loc)
				break;
		}
		/* If not, allocate a new one */
		if (j >= p->no_tpoints) {
			p->no_tpoints++;
			if ((p->tpoints = (trans_point *)realloc(p->tpoints, sizeof(trans_point) * p->no_tpoints)) == NULL)
				error("Realloc of tpoints");
			p->tpoints[j].loc = s->tpoints[k].loc;
			for (i = 0; i < MAX_CHAN; i++)
				p->tpoints[j].val[i] = -1.0;
		}
		for (i = 0; i < MAX_CHAN; i++) {
			if (s->tpoints[k].val[i] >= 0.0)
				p->tpoints[j].val[i] = s->tpoints[k].val[i]; 
		}
	}
	if (s->no_tpoints > 0) {
		/* Sort the transfer points into loc order */
#define HEAP_COMPARE(A,B) ((A).loc < (B).loc)
		HEAPSORT(trans_point, p->tpoints, p->no_tpoints);
#undef HEAP_COMPARE
	}
}

/* Create a new, empty pcaltarget */
/* Return NULL on error */
pcaltarg *new_pcaltarg() {
	pcaltarg *p;

	if ((p = (pcaltarg *)calloc(1, sizeof(pcaltarg))) == NULL) {
		return NULL;
	}

	/* Set method pointers */
	p->del = pcaltarg_del;
	p->write = pcaltarg_write;
	p->read = pcaltarg_read;
	p->update_devmax = pcaltarg_update_devmax;
	p->update_ademax = pcaltarg_update_ademax;
	p->update_ademin = pcaltarg_update_ademin;
	p->update_tcurve = pcaltarg_update_tcurve;
	p->is_set = pcaltarg_is_set;
	p->update = pcaltarg_update;

	return p;
}

/* - - - - - - - - - - - - - - - - - - - - - - - */
/* Colors to use with do_plot_gen() */
float cal_plot_colors[16][3] = {
	{   0/255.0,  10/255.0, 255/255.0},	/*  0 Blue */
	{ 210/255.0,  30/255.0,   0/255.0},	/*  1 Red */
	{ 200/255.0, 200/255.0,   0/255.0},	/*  2 Yellow */
	{   0/255.0,   0/255.0,   0/255.0},	/*  3 Black */
	{   0/255.0, 200/255.0,  90/255.0},	/*  4 Green */
	{ 220/255.0,   0/255.0, 255/255.0},	/*  5 Purple */
	{ 136/255.0,  86/255.0,  68/255.0},	/*  6 Brown */
	{ 248/255.0,  95/255.0,   0/255.0},	/*  7 Orange */
	{ 160/255.0, 160/255.0, 160/255.0},	/*  8 Grey */
	{ 220/255.0,  30/255.0, 220/255.0},	/*  9 Magenta */
	{ 112/255.0, 255/255.0, 161/255.0},	/* 10 Lime */
	{ 255/255.0, 191/255.0,  80/255.0},	/* 11 Pink */
	{   0/255.0, 100/255.0, 100/255.0},	/* 12 Full Cyan */
	{ 100/255.0,   0/255.0, 100/255.0},	/* 13 Full Magenta */
	{ 100/255.0, 100/255.0,   0/255.0},	/* 14 Full Yellow */
	{ 100/255.0,   0/255.0,   0/255.0}	/* 15 Full Red */
};

/* - - - - - - - - - - - - - - - - - - - - - - - */

/* A wedge sample value */
typedef struct {
	double inv;			/* Input value (cal table, linear with index) */
	double dev;			/* Device value (cal table = calibrated output) */
	double XYZ[3];		/* XYZ value */
	double Lab[3];		/* Lab value */
} wval;

#define MAX_INVSOLN	10	/* Rspl maximum reverse solutions */

/* rspl setting functions */
static void rsplset1(void *cbntx, double *out, double *in) {
	co *dpoints = (co *)cbntx;
	int ix;

	ix = *((int*)&in[-0-1]);	/* Get grid index being looked up */
	out[0] = dpoints[ix].v[0];
}

/* Do an inverse lookup of an rspl. Return -1.0 on error. */
/* dir is value to favour if there are multiple solutions. */
static double rspl_ilookup(rspl *r, double dir, double in) {
	int nsoln;				/* Number of solutions found */
	co pp[MAX_INVSOLN];		/* Room for all the solutions found */
	int k;					/* Chosen solution */

	pp[0].v[0] = in;

	nsoln = r->rev_interp (
		r,				 	/* this */
		RSPL_NEARCLIP,		/* Clip to nearest (faster than vector) */
		MAX_INVSOLN,		/* Maximum number of solutions allowed for */
		NULL, 				/* No auxiliary input targets */
		NULL,				/* Clip vector direction and length */
		pp);				/* Input and output values */

	nsoln &= RSPL_NOSOLNS;		/* Get number of solutions */

	if (nsoln == 1) { /* Exactly one solution */
		k = 0;
	} else if (nsoln == 0) {	/* Zero solutions. This is unexpected. */
		return -1.0;
	} else {		/* Multiple solutions */
		double bdist = 1e300;
		int bsoln = 0;
//		warning("Multiple solutions for curve %d for DE %f",j,pp[0].v[0]); 
		for (k = 0; k < nsoln; k++) {
			double tt;
			tt = pp[k].p[0] - dir;
			tt *= tt;
			if (tt < bdist) {	/* Better solution */
				bdist = tt;
				bsoln = k;
			}
		}
		k = bsoln;
	}
	return pp[k].p[0];
}

/* Given a raw device value, lookup the calibrated device value value */
static double interp_cal(wval *cvals, int n_cvals, double ival) {
	int mi, n_cvals_1 = n_cvals-1;
	double t, w, oval;

	t = (n_cvals-1.0) * ival;
	mi = (int)floor(t);			/* Grid coordinate */
	if (mi < 0)					/* Limit to valid index range */
		mi = 0;
	else if (mi >= n_cvals_1)
		mi = n_cvals_1-1;
	w = t - (double)mi;	 		/* 1.0 - weight */

	oval = (1.0 - w) * cvals[mi].dev + w * cvals[mi+1].dev;

	return oval;
}

int main(int argc, char *argv[]) {
	int fa,nfa,mfa;				/* current argument we're looking at */
	int verb = 0;
	int doplot = 0;				/* nz to plot curves, 2 to plot more details */
	int do3dplot = 0;			/* nz to save 3D plot of raw & calibrated colorant loci */
	int initial = 0;			/* Do initial creation of cal target and calibration */
	int recal = 0;				/* Do recalibrate/use cal target. */
	int verify = 0;				/* Do verification */
	int imitate = 0;			/* Do target directly from input */
	int dowrite = 1;			/* Write to files */
	int doamp = 0;				/* Write Adobe Photoshop .AMP file */
	int verified = 1;			/* Set to 0 if verification failed */
	profxinf xpi;				/* Extra profile/calibration information */
	pcaltarg *upct = NULL;		/* User settings of print calibration target */
	pcaltarg *pct = NULL;		/* Settings of print calibration target */
	int cal_res = DEF_CAL_RES;	/* Calibration file resolution */
	double smooth = RSPLSMOOTH;	/* RSPL Smoothness factor */
	double xsmooth = 1.0;		/* Smoothing multiplier */
	double ver_maxde = 2.0;		/* Verify maximum Delta E (1.0 for smooth == 1.0) */
	int spec = 0;				/* Use spectral data flag */
	icxIllumeType illum = icxIT_D50;	/* Spectral defaults */
	xspect cust_illum;			/* Custom illumination spectrum */
	icxObserverType observ = icxOT_CIE_1931_2;	/* The classic observer */

	char baname[MAXNAMEL+1] = "";	/* Input & Output base name */
	char inname[MAXNAMEL+1] = "";	/* new .ti3 input file name */
	char calname[MAXNAMEL+1] = "";	/* previous .cal input file name */
	char outname[MAXNAMEL+1] = "";	/* new .cal output file name */
	char ampname[MAXNAMEL+1] = "";	/* new .amp output file name */
	double maxscale[MAX_CHAN];	/* Scale auto device maximum to % */
	cgats *icg = NULL;			/* .ti3 input cgats structure */
	int ti;						/* Temporary CGATs index */
	inkmask devmask;			/* ICX ink mask of device space */
	int devchan;				/* Number of chanels in device space */
	int isLab = 0;				/* Flag indicating whether PCS is XYZ or Lab */
	int n_pvals[MAX_CHAN];		/* Number of measurement values */
	wval *pvals[MAX_CHAN];		/* Patch measurement values */
	wval white;					/* Average white XYZ and L*a*b* value */
	int n_white = 0;			/* Number of values to average */
	icmXYZNumber wht;			/* White value used for paper relative L*a*b* conversion */
	rspl *raw[MAX_CHAN];		/* Raw L*a*b* values fitted to rspl */
	rspl *ade[MAX_CHAN];		/* Absolute delta E76 vs device value */
	rspl *rde[MAX_CHAN];		/* Relative delta E94 vs device value */
	rspl *dE762dE94[MAX_CHAN];	/* Informational conversion from Absolute dE76 to dE94 */
	double mxade[MAX_CHAN];		/* Maximum discovvered ade value */
	double mxdev[MAX_CHAN] = { 0 } ;	/* Correspondin maximum device value (initial only) */
	rspl *pcade[MAX_CHAN];		/* Previous calibrated absolute delta E76 */
	double idpow[MAX_CHAN] = { -1.0 };		/* Ideal power-like of targen values */
	int n_cvals;				/* Number of calibration curve values */
	wval *cvals[MAX_CHAN];		/* Calibration curve tables */
	rspl *tcurves[MAX_CHAN];	/* Target tweak/shape curves */
	int i, j;


	/* Init pointers to NULL */
	for (j = 0; j < MAX_CHAN; j++) {
		maxscale[j] = -1.0;
		pvals[j] = NULL;
		raw[j] = NULL;
		ade[j] = NULL;
		rde[j] = NULL;
		dE762dE94[j] = NULL;
		pcade[j] = NULL;
		cvals[j] = NULL;
		tcurves[j] = NULL;
	}

	error_program = argv[0];
	memset((void *)&xpi, 0, sizeof(profxinf));	/* Init extra profile info to defaults */
	if ((upct = new_pcaltarg()) == NULL || (pct = new_pcaltarg()) == NULL)
		error("new_caltarg failed");

	if (argc < 3)
		usage("Too few arguments, got %d expect at least %d",argc-1,2);

#ifdef NEVER
	{
		double src, dst, pp;

		src = 0.5;
		dst = 0.25;
		pp = icx_powlike_needed(src, dst); 
		printf("%f -> %f needs %f, check %f\n",src,dst,pp,icx_powlike(src,pp));
		
		src = 0.25;
		dst = 0.5;
		pp = icx_powlike_needed(src, dst); 
		printf("%f -> %f needs %f, check %f\n",src,dst,pp,icx_powlike(src,pp));

		src = 0.5;
		dst = 0.707106;
		pp = icx_powlike_needed(src, dst); 
		printf("%f -> %f needs %f, check %f\n",src,dst,pp,icx_powlike(src,pp));

		src = 0.5;
		dst = 0.5;
		pp = icx_powlike_needed(src, dst); 
		printf("%f -> %f needs %f, check %f\n",src,dst,pp,icx_powlike(src,pp));
	}
#endif // NEVER

	/* Process the arguments */
	mfa = 1;		/* Minimum final arguments */
	for (fa = 1;fa < argc;fa++) {

		nfa = fa;					/* skip to nfa if next argument is used */
		if (argv[fa][0] == '-') {	/* Look for any flags */
			char *na = NULL;		/* next argument after flag, null if none */

			if (argv[fa][2] != '\000')
				na = &argv[fa][2];		/* next is directly after flag */
			else {
				if ((fa+1+mfa) < argc) {
					if (argv[fa+1][0] != '-') {
						nfa = fa + 1;
						na = argv[nfa];		/* next is seperate non-flag argument */
					}
				}
			}

			if (argv[fa][1] == '?')
				usage("Usage requested");

			else if (argv[fa][1] == 'v') {
				if (na != NULL) {
					fa = nfa;
					verb = atoi(na);
				} else
					verb = 1;
			}

			else if (argv[fa][1] == 'p') {
				if (na != NULL) {
					fa = nfa;
					doplot = atoi(na);
				} else
					doplot = 1;				/* Plot various graphs */
			}

			else if (argv[fa][1] == 'w') {
				do3dplot = 1;				/* Plot input in 3D */
			}

			else if (argv[fa][1] == 'i') {
				initial = 1;			/* Initial calibration */
				recal = 0;
				verify = 0;
				imitate = 0;
				mfa = 1;
			}

			else if (argv[fa][1] == 'r') {
				initial = 0;
				recal = 1;			/* Recalibrate */
				verify = 0;
				imitate = 0;
				mfa = 2;
			}

			else if (argv[fa][1] == 'e') {
				initial = 0;
				recal = 0;
				verify = 1;			/* Verify */
				imitate = 0;
				mfa = 2;
			}

			else if (argv[fa][1] == 'I') {
				initial = 0;
				recal = 0;
				verify = 0;
				imitate = 1;			/* Imitation target */
				mfa = 1;
			}

			else if (argv[fa][1] == 'd')
				dowrite = 0;			/* Don't write to files */

			else if (argv[fa][1] == 'a')
				doamp = 1;			/* write AMP file */

			/* Calibration file resolution */
			else if (argv[fa][1] == 'z') {
				if (na == NULL) usage("Expect argument to resolution flag -z");
				fa = nfa;
				cal_res = atoi(na);
				if (cal_res < 16 | cal_res > MAX_CAL_RES) 
					usage("resolution %d out of range 16..%d",cal_res, MAX_CAL_RES);
			}

			/* Smoothing modfider */
			else if (argv[fa][1] == 's') {
				if (na == NULL) usage("Expect argument to smoothing flag -s");
				fa = nfa;
				xsmooth = atof(na);
			}

			/* Manufacturer description string */
			else if (argv[fa][1] == 'A') {
				if (na == NULL) usage("Expect argument to manufacturer description flag -A");
				fa = nfa;
				xpi.deviceMfgDesc = na;
			}

			/* Model description string */
			else if (argv[fa][1] == 'M') {
				if (na == NULL) usage("Expect argument to model description flag -M");
				fa = nfa;
				xpi.modelDesc = na;
			}

			/* Profile Description */
			else if (argv[fa][1] == 'D') {
				if (na == NULL) usage("Expect argument to profile description flag -D");
				fa = nfa;
				xpi.profDesc = na;
			}

			/* Copyright string */
			else if (argv[fa][1] == 'C') {
				if (na == NULL) usage("Expect argument to copyright flag -C");
				fa = nfa;
				xpi.copyright = na;
			}

			/* Per channel target modifiers */
			else if (argv[fa][1] == 'x'
			      || argv[fa][1] == 'm'
			      || argv[fa][1] == 'n' 
			      || argv[fa][1] == 't') {
				char fch = argv[fa][1];
				int chan = -1;
				double val = -1.0;
				if (na == NULL)
					usage("Expect channel flag after flag -%c",argv[fa][1]);
				fa = nfa;
    			switch (na[0]) {
					case 'c': case 'r': case '0':
						chan = 0;
						break;
					case 'm': case 'g': case '1':
						chan = 1;
						break;
					case 'y': case 'b': case '2':
						chan = 2;
						break;
					case 'k': case '3':
						chan = 3;
						break;
					case '4':
						chan = 4;
						break;
					case '5':
						chan = 5;
						break;
					case '6':
						chan = 6;
						break;
					case '7':
						chan = 7;
						break;
					case '8':
						chan = 8;
						break;
					case '9':
						chan = 9;
						break;
					case 'A':
						chan = 10;
						break;
					case 'B':
						chan = 11;
						break;
					case 'C':
						chan = 12;
						break;
					case 'D':
						chan = 13;
						break;
					case 'E':
						chan = 14;
						break;
					case 'F':
						chan = 15;
						break;
					default:
						usage("Unknown channel flag '%c' after flag -%c",argv[fa][2],argv[fa][1]);
				}
				++fa;
				if (fa >= argc || argv[fa][0] == '-') usage("Expect argument after flag -%c%c",fch,na[0]);
				val = atof(argv[fa]);
			
				if (fch == 'x') {
					if (val < 0.0 || val > 100.0)
						usage("Argument to -%c%c %f from '%s' is out of range",fch,na[0],val,argv[fa]);
					val /= 100.0;
					upct->update_devmax(upct, chan, val);

				} else if (fch == 'm') {
					if (val < 0.0 || val > 100.0)
						usage("Argument to -%c%c %f from '%s' is out of range",fch,na[0],val,argv[fa]);
					val /= 100.0;
					maxscale[chan] = val;

				} else if (fch == 'n') {
					upct->update_ademin(upct, chan, val);

				} else if (fch == 't') {
					if (val < 0.0 || val > 100.0)
						usage("Argument to -%c%c %f from '%s' is out of range",fch,na[0],val,argv[fa]);
					val /= 100.0;
					upct->update_tcurve(upct, chan, 0.5, val);
				}
			}
			else 
				usage("Unknown flag '%c'",argv[fa][1]);
		} else
			break;
	}


	smooth *= xsmooth;

	if (!(   (initial && !recal && !verify && !imitate)
	      || (!initial && recal && !verify && !imitate)
	      || (!initial && !recal && verify && !imitate)
	      || (!initial && !recal && !verify && imitate)))
		error("One of -i, -r -e or -I must be set");

	/* Get the file name arguments */
	if (verify || recal) {
		if (fa >= argc || argv[fa][0] == '-') usage("Missing prevoius .cal basename");
		strncpy(calname,argv[fa++],MAXNAMEL-4); calname[MAXNAMEL-4] = '\000';
		strcat(calname,".cal");
	}
	if (fa >= argc || argv[fa][0] == '-') usage("Missing .ti3 and new .cal basename");
	strncpy(baname,argv[fa++],MAXNAMEL-4); baname[MAXNAMEL-4] = '\000';
	strcpy(inname,baname);		/* new .ti3 file */
	strcat(inname,".ti3");
	strcpy(outname,baname);		/* New .cal file */
	strcat(outname,".cal");
	strcpy(ampname,baname);		/* New .amp file */
	strcat(ampname,".amp");

	if (fa < argc) usage("Too many arguments ('%s')",argv[fa]);

	/* Open and look at the .ti3 profile patches file */
	icg = new_cgats();				/* Create a CGATS structure */
	icg->add_other(icg, "CTI3"); 	/* our special input type is Calibration Target Information 3 */

	if (icg->read_name(icg, inname))
		error("CGATS file read error : %s",icg->e.m);

	if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0)
		error ("Input file isn't a CTI3 format file");
	if (icg->ntables < 1)
		error ("Input file doesn't contain at least one table");

	/* See if CIE is actually available - some sources of .TI3 don't provide it */
	if (!spec
	 && icg->find_field(icg, 0, "LAB_L") < 0
	 && icg->find_field(icg, 0, "XYZ_X") < 0) {

		if (icg->find_kword(icg, 0, "SPECTRAL_BANDS") < 0)
			error ("Neither CIE nor spectral data found in file '%s'",inname);

		/* Switch to using spectral information */
		if (verb)
			printf("No CIE data found, switching to spectral with standard observer & D50\n");
		spec = 1;
		illum = icxIT_D50;
		observ = icxOT_CIE_1931_2;
	}
	
	/* If we requested spectral, check that it is available */
	if (spec) {
		if (icg->find_kword(icg, 0, "SPECTRAL_BANDS") < 0)
			error ("Requested spectral interpretation when data not available");
	}


	/* Get colorspace information from input CGATS file */
	{
		char *buf;
		char *inc, *outc;

		if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
			error("Input file doesn't contain keyword COLOR_REPS");

		if ((buf = strdup(icg->t[0].kdata[ti])) == NULL)
			error("Malloc failed - color rep");

		/* Split COLOR_REP into device and PCS space */
		inc = buf;
		if ((outc = strchr(buf, '_')) == NULL)
			error("COLOR_REP '%s' invalid", icg->t[0].kdata[ti]);
		*outc++ = '\000';

		if (strcmp(outc, "XYZ") == 0)
			isLab = 0;
		else if (strcmp(outc, "LAB") == 0)
			isLab = 1;
		else
			error("COLOR_REP '%s' invalid (Neither XYZ nor LAB)", icg->t[0].kdata[ti]);

		devmask = icx_char2inkmask(inc); 
		devchan = icx_noofinks(devmask);
		
		if (devchan == 0)
			error("COLOR_REP '%s' invalid (No matching devmask)", icg->t[0].kdata[ti]);
		
		if ((devmask & ICX_ADDITIVE) && !(devmask & ICX_INVERTED))
			warning("COLOR_REP '%s' is probably not suitable for print calibration!", icg->t[0].kdata[ti]);

		free(buf);
	}

	if (verify || recal || imitate) {
		if (upct->is_set(upct)) {
			warning("Command line calibration target paramers ignored on re-calibrate, verify and imitate!");
		}
	}

	/* For recalibrate or verify, load the previous calibration file */
	if (verify || recal) {
		cgats *tcg;						/* Previous .cal file */

		tcg = new_cgats();				/* Create a CGATS structure */
		tcg->add_other(tcg, "CAL"); 	/* our special input type is Calibration Target */
		tcg->add_other(tcg, "CALDEEQ");

		if (tcg->read_name(tcg, calname))
			error("No cal target '%s' found for re-calibrate (%s)\n",calname,tcg->e.m);

		/* Check that this is an output cal file */
		if ((ti = tcg->find_kword(tcg, 0, "DEVICE_CLASS")) < 0)
			error ("Calibration file '%s'doesn't contain keyword DEVICE_CLASS",calname);
		if (strcmp(tcg->t[0].kdata[ti],"OUTPUT") != 0)
			error ("Calibration file '%s' doesn't has DEVICE_CLASS that is not OUTPUT",calname);

		if (pct->read(pct, tcg, 1) != 0)
			error("Reading cal target '%s' failed",calname);

		if (pct->devmask != devmask)
			error("Target '%s' colorspace '%s' doesn't match '%s' colorspace '%s'",
				   calname,icx_inkmask2char(pct->devmask, 1),inname,icx_inkmask2char(devmask, 1)); 

		/* Load the previous expected absolute DE response. */
		/* We use this to reproduce a calibrated response, even though absolute DE */
		/* is not the parameter that is being linearized. */
		/* It will be in the third table with other type "CAL" */
		if (tcg->ntables >= 3 && tcg->t[2].tt == tt_other && tcg->t[2].oi == 0) {
			int ti;
			char *bident;
			int spi[1+MAX_CHAN];	/* CGATS indexes for each field */
			char buf[100];

			bident = icx_inkmask2char(pct->devmask, 0); 

			if (tcg->t[2].nsets <= 0)
				error ("No Calibration Expected DE Response in '%s'",calname);

			/* Figure out the indexes of all the fields */
			sprintf(buf, "%s_I_DE",bident);
			if ((spi[0] = tcg->find_field(tcg, 2, buf)) < 0)
				error("Can't find field %s in '%s' table 3",buf,calname);

			for (i = 0; i < devchan; i++) {
				inkmask imask = icx_index2ink(pct->devmask, i);
				sprintf(buf, "%s_%s_DE",bident,icx_ink2char(imask));
				if ((spi[1+i] = tcg->find_field(tcg, 2, buf)) < 0)
					error("Can't find field %s in '%s' table 3",buf,calname);
			}

			/* Read in each channels values and put them in a rspl */
			for (j = 0; j < devchan; j++) {
				datai low,high;
				int gres[1];
				co *dpoints;

				low[0] = 0.0;
				high[0] = 1.0;
				gres[0] = tcg->t[2].nsets;

				if ((pcade[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
					error("new_rspl() failed");

				if ((dpoints = malloc(sizeof(co) * gres[0])) == NULL)
					error("malloc dpoints[%d] failed",gres[0]);

				/* Copy the points to our array */
				if (devmask & ICX_ADDITIVE) {
					for (i = 0; i < gres[0]; i++) {
						dpoints[i].p[0] = 1.0 - i/(double)(gres[0]-1);	/* (ignored) */
						dpoints[i].v[0] = *((double *)tcg->t[2].fdata[gres[0]-1-i][spi[1+j]]);
					}
				} else {
					for (i = 0; i < gres[0]; i++) {
						dpoints[i].p[0] = i/(double)(gres[0]-1);	/* (ignored) */
						dpoints[i].v[0] = *((double *)tcg->t[2].fdata[i][spi[1+j]]);
					}
				}

				pcade[j]->set_rspl(pcade[j],
						   0, 
						   (void *)dpoints,		/* Read points */
						   rsplset1,			/* Setting function */
						   low, high, gres,		/* Low, high, resolution of grid */
						   NULL, NULL			/* Default data scale */
						   );
				free(dpoints);
			}
			free(bident);
		}

		/* Load the previous adE76 to rdE94 equivalence curves. */
		/* We use this to be able to show the equivalent rdE94 response */
		/* when we're actually calibrating to a previous or imitation aDE76 target. */
		if (tcg->ntables >= 4 && tcg->t[3].tt == tt_other && tcg->t[3].oi == 1) {
			int ti;
			char *bident;
			int spi[1+2*MAX_CHAN];	/* CGATS indexes for each field */
			char buf[100];

			bident = icx_inkmask2char(pct->devmask, 0); 

			if (tcg->t[3].nsets <= 0)
				error ("No Calibration adE76 to rdE94 equivalence curves in '%s'",calname);

			/* Figure out the indexes of all the fields */
			if ((spi[0] = tcg->find_field(tcg, 3, "ID")) < 0)
				error("Can't find field %s in '%s' table 4","ID",calname);

			for (i = 0; i < devchan; i++) {
				inkmask imask = icx_index2ink(pct->devmask, i);
				sprintf(buf, "%s_%s_ADE",bident,icx_ink2char(imask));
				if ((spi[1+2*i+0] = tcg->find_field(tcg, 3, buf)) < 0)
					error("Can't find field %s in '%s' table 4",buf,calname);

				sprintf(buf, "%s_%s_RDE",bident,icx_ink2char(imask));
				if ((spi[1+2*i+1] = tcg->find_field(tcg, 3, buf)) < 0)
					error("Can't find field %s in '%s' table 4",buf,calname);
			}

			/* Read in each channels values and put them in a rspl */
			for (j = 0; j < devchan; j++) {
				datai low, high;
				int gres[1];
				co *dpoints;

				gres[0] = tcg->t[3].nsets;

				if ((dE762dE94[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
					error("new_rspl() failed");

				if ((dpoints = malloc(sizeof(co) * gres[0])) == NULL)
					error("malloc dpoints[%d] failed",gres[0]);

				/* Copy the points to our array */
				for (i = 0; i < gres[0]; i++) {
					dpoints[i].p[0] = *((double *)tcg->t[3].fdata[i][spi[1+2*j+0]]);
					dpoints[i].v[0] = *((double *)tcg->t[3].fdata[i][spi[1+2*j+1]]);
				}

				mxade[j] = dpoints[gres[0]-1].p[0]; 

				low[0] = 0.0;
				high[0] = mxade[j];

				dE762dE94[j]->set_rspl(dE762dE94[j],
						   0, 
						   (void *)dpoints,		/* Read points */
						   rsplset1,			/* Setting function */
						   low, high, gres,		/* Low, high, resolution of grid */
						   NULL, NULL			/* Default data scale */
						   );
				free(dpoints);
			}
			free(bident);
		}
		tcg->del(tcg);

	} else {	/* Must be an initial or imitation calibration */

		pct->devmask = devmask;

		/* Set the cal target from any user supplied parameters */
		pct->update(pct, upct);

		/* No previous absolute de reference */
		for (j = 0; j < devchan; j++)
			pcade[j] = NULL;
	}

	/* Common processing: */

	/* Read in the patch data */
	{
		char buf[100];
		char *pcsfname[2][3] = { { "XYZ_X", "XYZ_Y", "XYZ_Z" },
		                         { "LAB_L", "LAB_A", "LAB_B" } };
		int dvi[MAX_CHAN];	/* CGATS indexes for each device field */
		int pcsix[3];		/* XYZ/Lab chanel indexes */
		xsp2cie *sp2cie = NULL;		/* Spectral conversion object */
		xspect sp;
		int  spi[XSPECT_MAX_BANDS];	/* CGATS indexes for each wavelength */
		char *bident = icx_inkmask2char(devmask, 0); 
	
		/* Figure out the indexes of all the device fields */
		for (j = 0; j < devchan; j++) {
			inkmask imask = icx_index2ink(devmask, j);
			sprintf(buf, "%s_%s",bident,icx_ink2char(imask));
			if ((dvi[j] = icg->find_field(icg, 0, buf)) < 0)
				error("Can't find field %s in '%s'",buf,inname);
#ifdef DEBUG
			printf("devn chan %d field %s = %d\n",j,buf,dvi[j]);
#endif
		}
		free(bident);

		if (spec) {
			int ii;
			char buf[100];

			if ((ii = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0)
				error ("Input file doesn't contain keyword SPECTRAL_BANDS");
			sp.spec_n = atoi(icg->t[0].kdata[ii]);
			if ((ii = icg->find_kword(icg, 0, "SPECTRAL_START_NM")) < 0)
				error ("Input file doesn't contain keyword SPECTRAL_START_NM");
			sp.spec_wl_short = atof(icg->t[0].kdata[ii]);
			if ((ii = icg->find_kword(icg, 0, "SPECTRAL_END_NM")) < 0)
				error ("Input file doesn't contain keyword SPECTRAL_END_NM");
			sp.spec_wl_long = atof(icg->t[0].kdata[ii]);
			sp.norm = 100.0;

			/* Find the fields for spectral values */
			for (j = 0; j < sp.spec_n; j++) {
				int nm;
		
				/* Compute nearest integer wavelength */
				nm = (int)(sp.spec_wl_short + ((double)j/(sp.spec_n-1.0))
				            * (sp.spec_wl_long - sp.spec_wl_short) + 0.5);
				
				sprintf(buf,"SPEC_%03d",nm);

				if ((spi[j] = icg->find_field(icg, 0, buf)) < 0)
					error("Input file doesn't contain field %s",buf);
			}

			/* Create a spectral conversion object to XYZ */
			if ((sp2cie = new_xsp2cie(illum, 0.0, &cust_illum, observ, NULL, icSigXYZData, icxClamp)) == NULL)
				error("Creation of spectral conversion object failed");

			/* To add FWA comp. would have to locate/create spectral white here, */
			/* then set the FWA comp. on. */
			/* See profout.c */

		} else {
			/* Figure out the indexes of the PCS fields */
			for (j = 0; j < 3; j++) {
				if ((i = icg->find_field(icg, 0, pcsfname[isLab][j])) >= 0) {
					if (icg->t[0].ftype[i] != r_t)
						error ("Field %s is wrong type",pcsfname[isLab][j]);
					pcsix[j] = i;
#ifdef DEBUG
					printf("PCS chan %d field %s = %d\n",j,pcsfname[isLab][j],pcsix[j]);
#endif
				} else {
					error ("Failed to find field %s",pcsfname[isLab][j]);
				}
			}
		}
	
		n_cvals = 0;
		for (j = 0; j < devchan; j++) {
			pvals[j] = NULL;
			n_pvals[j] = 0;
			cvals[j] = NULL;
		}

		/* Read all the test patches in */
		for (i = 0; i < icg->t[0].nsets; i++) {
			double maxv = -1.0;
			int maxch;

#ifdef DEBUG
			printf("Reading patch %d\n",i);
#endif

			/* Locate the maximum device value of any channel */
			for (j = 0; j < devchan; j++) {
				double val = *((double *)icg->t[0].fdata[i][dvi[j]]) / 100.0;
				if (devmask & ICX_ADDITIVE)
					val = 1.0 - val;
				if (val > maxv) {
					maxv = val;
					maxch = j;
				}
			}
#ifdef DEBUG
			printf("max %f at chan %d\n",maxv,maxch);
#endif
			/* Treat white specially, and take it out of the list */
			if (maxv < 1e-6) {
				double wxyz[3];
				if (n_white == 0) {
					white.dev = 0.0;
					for (j = 0; j < 3; j++)
						white.XYZ[j] = 0.0;
				}
				if (spec) {
					/* Read and convert the spectral value */
					for (j = 0; j < sp.spec_n; j++)
						sp.spec[j] = *((double *)icg->t[0].fdata[i][spi[j]]);

					sp2cie->convert(sp2cie, wxyz, &sp);

				} else {
					/* Read the CIE value */
					for (j = 0; j < 3; j++)
						wxyz[j] = *((double *)icg->t[0].fdata[i][pcsix[j]]);

					/* And convert to XYZ 0..1 */
					if (isLab) {
						icmLab2XYZ(&icmD50, wxyz, wxyz);
					} else {
						for (j = 0; j < 3; j++)
							wxyz[j] /= 100.0;
					}
				}
			
				white.dev += maxv;
				for (j = 0; j < 3; j++)
					white.XYZ[j] += wxyz[j];
				n_white++;
#ifdef DEBUG
				printf("  white: dev %f,XYZ %f %f %f\n",
				white.dev,white.XYZ[0]/n_white, white.XYZ[1]/n_white, white.XYZ[2]/n_white);
#endif
			} else {
				wval *vp;
				/* Check that all the non-max value channels are zero */
				for (j = 0; j < devchan; j++) {
					double val = *((double *)icg->t[0].fdata[i][dvi[j]]) / 100.0;
					if (devmask & ICX_ADDITIVE)
						val = 1.0 - val;
					if (j == maxch)
						continue;
					if (val > 0.001)
						break;
				}
				if (j < devchan) {
#ifdef DEBUG
				printf("Skipping patch\n");
#endif
					continue;		/* Ignore this patch */
				}

				if ((pvals[maxch] = (wval *)realloc(pvals[maxch],
				                       sizeof(wval) * (n_pvals[maxch]+1))) == NULL)
					error("Realloc of pvals failed");
				vp = &pvals[maxch][n_pvals[maxch]];
				vp->dev = maxv;

				if (spec) {
					/* Read and convert the spectral value */
					for (j = 0; j < sp.spec_n; j++)
						sp.spec[j] = *((double *)icg->t[0].fdata[i][spi[j]]);

					sp2cie->convert(sp2cie, vp->XYZ, &sp);

				} else {
					/* Read the CIE value */
					for (j = 0; j < 3; j++)
						vp->XYZ[j] = *((double *)icg->t[0].fdata[i][pcsix[j]]);

					/* And convert to XYZ 0..1 */
					if (isLab) {
						icmLab2XYZ(&icmD50, vp->XYZ, vp->XYZ);
					} else {
						for (j = 0; j < 3; j++)
							vp->XYZ[j] /= 100.0;
					}
				}
				/* Temporary D50 Lab */
				icmXYZ2Lab(&icmD50, vp->Lab, vp->XYZ);
#ifdef DEBUG
				printf("  patch %d: dev %f,XYZ %f %f %f, D50 Lab %f %f %f\n",
				n_pvals[maxch], vp->dev,vp->XYZ[0], vp->XYZ[1], vp->XYZ[2],
				vp->Lab[0], vp->Lab[1], vp->Lab[2]);
#endif
				n_pvals[maxch]++;
			}
		}

		/* Average the white */
		if (n_white == 0)
			error("Can't find even one white patch in '%s'",inname);
#ifdef DEBUG
			printf("% white patches\n",n_white);
#endif
		for (j = 0; j < 3; j++) {
			white.dev    /= (double)n_white;
			white.XYZ[j] /= (double)n_white;
		}
		icmXYZ2Lab(&icmD50, white.Lab, white.XYZ);

		if (verb) {
			icmXYZ2Lab(&icmD50, white.Lab, white.XYZ);
			printf("Average white = XYZ %f %f %f, D50 Lab %f %f %f\n",
			white.XYZ[0], white.XYZ[1], white.XYZ[2], white.Lab[0], white.Lab[1], white.Lab[2]);
		}

		icmAry2XYZ(wht, white.XYZ);	/* Use the average measured white */

		for (j = 0; j < devchan; j++) {
			wval *wp;

			/* Copy averaged white value back into each channel */
			if ((pvals[j] = (wval *)realloc(pvals[j],
			                       sizeof(wval) * (n_pvals[j]+1))) == NULL)
				error("Realloc (%d) of pvals failed",n_pvals[j]+1);
			wp = &pvals[j][n_pvals[j]];
			wp->dev = white.dev;
			wp->XYZ[0] = white.XYZ[0];
			wp->XYZ[1] = white.XYZ[1];
			wp->XYZ[2] = white.XYZ[2];
			n_pvals[j]++;

			/* Convert all the XYZ values to Lab paper relative */
			for (i = 0; i < n_pvals[j]; i++) {
				wp = &pvals[j][i];
				icmXYZ2Lab(&wht, wp->Lab, wp->XYZ);
			}

			/* Sort the channel according to device value */ 
			/* For a consistent result for identical device values, */
			/* secondary sort by inverse CIE value */
//#define HEAP_COMPARE(A,B) ((A).dev < (B).dev)
#define HEAP_COMPARE(A,B) ((A).dev != (B).dev ? ((A).dev < (B).dev) : ((A).Lab[0] > (B).Lab[0])) 
			HEAPSORT(wval, pvals[j], n_pvals[j]);
#undef HEAP_COMPARE

			/* Check the maximum value looks OK */
			if (n_pvals[j] < 5)
				warning("Channel %d has only %d test patches",n_pvals[j]);
			if (pvals[j][n_pvals[j]-1].dev < 0.99)
				warning("Channel %d has max test patch value of %f",pvals[j][n_pvals[j]-1]); 

			if (verb > 2) {
				printf("Chan %d has %d measurement values:\n",j,n_pvals[j]);
				for (i = 0; i < n_pvals[j]; i++) {
					wp = &pvals[j][i];
					printf("  %d: dev %f,XYZ %f %f %f, Lab %f %f %f\n",
					       i,wp->dev,wp->XYZ[0], wp->XYZ[1], wp->XYZ[2],
					         wp->Lab[0], wp->Lab[1], wp->Lab[2]);
				}
			}
		}

		if (sp2cie != NULL)
			sp2cie->del(sp2cie);
	}
	icg->del(icg);		/* Clean up */

	/* Interpolate Lab using rspl */
	for (j = 0; j < devchan; j++) {
		datai low,high;
		datao olow,ohigh;
		int gres[MXDI];
		double avgdev[MXDO];
		cow *dpoints;

		low[0] = 0.0;
		high[0] = 1.0;
		gres[0] = GRES;
		olow[0] = 0.0;
		ohigh[0] = 100.0;
		olow[1] = olow[2] = -128.0; 
		ohigh[1] = ohigh[2] = 128.0;
		avgdev[0] = 0.0025;
		avgdev[1] = 0.005;
		avgdev[2] = 0.005;

		if ((raw[j] = new_rspl(RSPL_NOFLAGS,1, 3)) == NULL)
			error("new_rspl() failed");

		if ((dpoints = (cow *)malloc(sizeof(cow) * n_pvals[j])) == NULL)
			error("malloc dpoints[%d] failed",n_pvals[j]);

		for (i = 0; i < n_pvals[j]; i++) {
			dpoints[i].p[0] = pvals[j][i].dev;
			dpoints[i].v[0] = pvals[j][i].Lab[0];
			dpoints[i].v[1] = pvals[j][i].Lab[1];
			dpoints[i].v[2] = pvals[j][i].Lab[2];
			if (i == 0)
				dpoints[i].w = (double)n_white;
			else
				dpoints[i].w = 1.0;
		}

		raw[j]->fit_rspl_w(raw[j],
		           RSPLFLAGS,
		           dpoints,			/* Test points */
		           n_pvals[j],			/* Number of test points */
		           low, high, gres,		/* Low, high, resolution of grid */
		           olow, ohigh,			/* Default data scale */
		           smooth,				/* Smoothing */
		           avgdev,				/* Average deviation */
		           NULL);				/* iwidth */


		/* Compute & show fit quality */
		if (verb > 0) {
			double avgde = 0.0, maxde = 0.0;
			for (i = 0; i < n_pvals[j]; i++) {
				co tp;	/* Test point */
				double de;
				tp.p[0] = pvals[j][i].dev;
				raw[j]->interp(raw[j], &tp);
				de = icmLabDE(pvals[j][i].Lab, tp.v);

				avgde += de;
				if (de > maxde)
					maxde = de;
			}
			avgde /= (double)n_pvals[j];
			printf("Chan %d raw fit avg DE %f, max %f\n",j,avgde,maxde);
		}

		free(dpoints);
	}

	/* Plot the raw data and fitted curves */
	if (doplot >= 2) {
		plot_g gg = { 0 };

		for (j = 0; j < devchan; j++) {
			int k;
			float rgb[3];

			for (k = 0; k < 3; k++) {	/* for L*m a*, b* */
				/* Make targets red, so they show when not covered by expected */
				rgb[0] = 1.0;
				rgb[1] = 0.0;
				rgb[2] = 0.0;
				if ((k % MXGPHS) == 1		/* Except redish colors */
				 || (k % MXGPHS) == 6
				 || (k % MXGPHS) == 7
				 || (k % MXGPHS) == 9
				 || (k % MXGPHS) == 11
				 || (k % MXGPHS) == 13
				 || (k % MXGPHS) == 15) {
					rgb[0] = 0.0;		/* where we make it blue */
					rgb[1] = 0.0;
					rgb[2] = 1.0;
				} 

				for (i = 0; i < n_pvals[j]; i++) {
					if (i == 0)
						start_line_g(&gg, pvals[j][i].dev, pvals[j][i].Lab[k], rgb);
					else
						extend_line_g(&gg, pvals[j][i].dev, pvals[j][i].Lab[k]);
				}

				for (i = 0; i < PRES; i++) {
					co tp;
					tp.p[0] = i/(double)(PRES-1);
					raw[j]->interp(raw[j], &tp);
					if (i == 0)
						start_line_g(&gg, tp.p[0], tp.v[k], cal_plot_colors[k % MXGPHS]);
					else
						extend_line_g(&gg, tp.p[0], tp.v[k]);
				}
			}
			printf("Chan %d raw and fitted L*a*b*:\n",j);
			do_plot_g(&gg, 0.0, 0.0, 0.0, 0.0, 1.0, 0, 1);

			clear_g(&gg);
		}
	}

	/* Create 3D plot of raw colorant loci */
	if (do3dplot) {
		char vname[MAXNAMEL+3] = "";
		vrml *wrl;
		int doaxes = 0;
		int devres = 21;

		strcpy(vname, baname);
		strcat(vname, "_r");

		if ((wrl = new_vrml(vname, doaxes, vrml_lab)) == NULL)
			error("new_vrml failed for '%s%s'",vname,vrml_ext());

		for (j = 0; j < devchan; j++) {
			double last[3];

			for (i = 0; i < devres; i++) {
				co tp;	/* Test point */
				tp.p[0] = i/(double)(devres-1);
				raw[j]->interp(raw[j], &tp);
				if (i > 0) {
					wrl->add_cone(wrl, last, tp.v, NULL, 20/(devres-1.0));
				}
				icmCpy3(last, tp.v);
			}
		}
		wrl->del(wrl);		/* Write file and delete */
	}

	/* Create a RSPL of absolute deltaE '76 and relative deltaE '94 */ 
	for (j = 0; j < devchan; j++) {
		datai low,high;
		int gres[MXDI];
		double avgdev[MXDO];
		co *dpoints_a;
		co *dpoints_r;
		double wh[3], prev[3], tot;

		low[0] = 0.0;
		high[0] = 1.0;
		gres[0] = GRES;
		avgdev[0] = 0.0;

		if ((ade[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
			error("new_rspl() failed");
		if (imitate) {		/* Use pcade to hold target to imitate, just like a recal */
			if ((pcade[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
				error("new_rspl() failed");
		}
		if ((rde[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
			error("new_rspl() failed");

		if ((dpoints_a = malloc(sizeof(co) * GRES)) == NULL)
			error("malloc dpoints[%d] failed",GRES);
		if ((dpoints_r = malloc(sizeof(co) * GRES)) == NULL)
			error("malloc dpoints[%d] failed",GRES);

//printf("~1 Chan %d:\n",j);
		for (i = 0; i < GRES; i++) {
			co tp;	/* Test point */

			tp.p[0] = i/(double)(GRES-1);
			raw[j]->interp(raw[j], &tp);

			dpoints_a[i].p[0] = tp.p[0];
			dpoints_r[i].p[0] = tp.p[0];
			if (i == 0) {
//printf("~1 wht = %f %f %f\n",tp.v[0],tp.v[1],tp.v[2]);
				tot = 0.0;
				prev[0] = wh[0] = tp.v[0];
				prev[1] = wh[1] = tp.v[1];
				prev[2] = wh[2] = tp.v[2];
				dpoints_a[i].v[0] = 0.0;
				dpoints_r[i].v[0] = 0.0;
			} else {
//printf("~1 samp %d = %f %f %f\n",i,tp.v[0],tp.v[1],tp.v[2]);
				/* Use Euclidean for large DE: (CIE94 stuffs up here) */
				dpoints_a[i].v[0] = icmLabDE(tp.v, wh);
				/* And CIE94 for small: */
				tot += icmCIE94(tp.v, prev);
				prev[0] = tp.v[0];
				prev[1] = tp.v[1];
				prev[2] = tp.v[2];
				dpoints_r[i].v[0] = tot;
			}
//printf("~1   %d: dev %f, ade %f, rde %f\n",i,tp.p[0],dpoints_a[i].v[0],dpoints_r[i].v[0]);
		}

		ade[j]->set_rspl(ade[j],
		           0, 
		           (void *)dpoints_a,	/* Test points */
		           rsplset1,			/* Setting function */
		           low, high, gres,		/* Low, high, resolution of grid */
		           NULL, NULL			/* Default data scale */
		           );
		if (imitate) {
			pcade[j]->set_rspl(pcade[j],
			           0, 
			           (void *)dpoints_a,	/* Test points */
			           rsplset1,			/* Setting function */
			           low, high, gres,		/* Low, high, resolution of grid */
			           NULL, NULL			/* Default data scale */
			           );
		}
		rde[j]->set_rspl(rde[j],
		           0, 
		           (void *)dpoints_r,		/* Test points */
		           rsplset1,			/* Setting function */
		           low, high, gres,		/* Low, high, resolution of grid */
		           NULL, NULL			/* Default data scale */
		           );
		free(dpoints_a);
		free(dpoints_r);
	}

	if (initial || imitate) {

		/* Find the absolute maximum aDE value for each curve. */
		/* This used for setting the max input range to dE762dE94, */
		/* and mxdev is used for limiting the device max to the monotic portion of the ade repsonse */
		for (j = 0; j < devchan; j++) {
			co tp;	/* Test point */
			mxade[j] = -1e6;
			for (i = 0; i < PRES; i++) {
				tp.p[0] = i/(double)(PRES-1);
				ade[j]->interp(ade[j], &tp);
				if (tp.v[0] > mxade[j]) {
					mxdev[j] = tp.p[0];
					mxade[j] = tp.v[0];
				}
			}
			if (verb > 1)
				printf("Chan %d device value limit %f, aDE76 %f:\n",j,mxdev[j], mxade[j]);
		}
	}

	if (initial) {

		/* Establish the ademax calibration aim values */
		pct->update_devmax(pct, -1, -1.0);		/* Make sure there is a value for each */
		pct->update_ademax(pct, -1, -1.0);
		pct->update_ademin(pct, -1, -1.0);
		for (j = 0; j < devchan; j++) {
			co tp;	/* Test point */

			if (pct->devmax[j] < 0.0) {		/* Auto */
				double maxd, maxde, maxix;
				/* Locate the point of maximum aDE */
				for (maxde = -1.0, i = 0; i < GRES; i++) {

					tp.p[0] = i/(GRES-1.0);
					ade[j]->interp(ade[j], &tp);
					if (tp.v[0] > maxde) {
						maxd = tp.p[0];
						maxde = tp.v[0];
						maxix = i;
					}
				}
				pct->devmax[j] = maxd;
				pct->ademax[j] = maxde;		/* Temporary */
//printf("Chan %d, dev %f, max de = %f\n", j, maxd, maxde);

				if (maxd < 0.2) {
					warning("Chan %d, max DE point %f is below < 0.2 - ignored\n", j, maxd);
					maxix = GRES-1;
				}
				/* Then locate the point below that where the slope */
				/* becomes reasonable. */ 
				for (i = maxix; i >= 40; i--) {
					double aslope, minslope = 1e6;
					double naslope, nminslope;
					int k;

					/* Compute the minimum over a span of 20/GRES */
					for (k = 0; k < 40; k++) {
						double dp, dv, slope;

						tp.p[0] = (i-k)/(GRES-1.0);
						dp = tp.p[0];
						ade[j]->interp(ade[j], &tp);
						dv = tp.v[0];

						tp.p[0] = (i-k-1)/(GRES-1.0);
						ade[j]->interp(ade[j], &tp);
						slope = (dv - tp.v[0])/(dp - (i-k-1)/(GRES-1.0));
						if (k == 0)
							aslope = slope;
//printf("  Chan %d, dev %f, dv = %f, slope = %f\n", j, (i-k)/(GRES-1.0),dv - tp.v[0],slope);
						if (slope < minslope)
							minslope = slope;
					}
//printf("Chan %d, dev %f, aslope = %f, min slope = %f\n", j, i/(GRES-1.0),aslope,minslope);
					/* Normalize the slopes */
					naslope = aslope * SLOPE_NORM/pct->ademax[j];
					nminslope = minslope * SLOPE_NORM/pct->ademax[j];
//printf("Chan %d, dev %f, norm aslope = %f, min slope = %f\n", j, i/(GRES-1.0),naslope,nminslope);

					if (naslope > MIN_SLOPE_A && nminslope >= MIN_SLOPE_O)
						break;
				}
				pct->devmax[j] = i/(GRES-1.0);

				/* Scale auto max device value */
				if (maxscale[j] >= 0.0)
					pct->devmax[j] *= maxscale[j];

			/* Manually set initial dev max */
			} else {
				if (maxscale[j] >= 0.0)
					warning("Chan %d, scale %.1f%% of auto max ignored since max override used\n", j, maxscale[j] * 100.0);
			}

			/* Sanity check the aim devmax */
			if (pct->devmax[j] > mxdev[j]) {
				warning("Chan %d, intended device max %f is beyond monotonicity limit of %f, so will be clipped",j,pct->devmax[j],mxdev[j]);
				pct->devmax[j] = mxdev[j];
			}

			/* Lookup devmax to set ademax */
			tp.p[0] = pct->devmax[j];
			ade[j]->interp(ade[j], &tp);
			pct->ademax[j] = tp.v[0];

			/* Establish a default ademin value */
			if (pct->ademin[j] < 0.0)
				pct->ademin[j] = 0.0;
		}
	}

	if (initial || imitate) {

		/* Create equivalence curves from absolute dE76 to relative dE94 for */
		/* use in information display */
		for (j = 0; j < devchan; j++) {
			datai low, high;
			int gres[1];
			co tp, *dpoints;

			low[0] = 0.0;
			high[0] = mxade[j];
			gres[0] = cal_res; 

			if ((dE762dE94[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
				error("new_rspl() failed");

			if ((dpoints = malloc(sizeof(co) * gres[0])) == NULL)
				error("malloc dpoints[%d] failed",gres[0]);

			/* Create points for our array */
			for (i = 0; i < gres[0]; i++) {

				dpoints[i].p[0] = high[0] * i/(double)(gres[0]-1);

				/* adE76 -> device */
				if ((tp.p[0] = rspl_ilookup(ade[j], 0.0, dpoints[i].p[0])) < 0.0)
					error("Unexpected failure to invert ade curve %d for DE %f",j,dpoints[i].p[0]); 
	
				/* Device -> rdE94 */
				rde[j]->interp(rde[j], &tp);

				dpoints[i].v[0] = tp.v[0];
			}

			dE762dE94[j]->set_rspl(dE762dE94[j],
					   0, 
					   (void *)dpoints,		/* Read points */
					   rsplset1,			/* Setting function */
					   low, high, gres,		/* Low, high, resolution of grid */
					   NULL, NULL			/* Default data scale */
					   );
			free(dpoints);
		}

	} else if (recal) {

		/* Since the plot markers use devmax, look it up */
		for (j = 0; j < devchan; j++) {
			if ((pct->devmax[j] = rspl_ilookup(ade[j], 0.5, pct->ademax[j])) < 0.0)
				error("Unexpected failure to invert curve %d for ADE %f",j,pct->ademax[j]); 
		}
	}

	/* Plot the adE76 to rdE94 curves */
	if (doplot >= 2) {
		plot_g gg = { 0 };
		co tp;

		for (j = 0; j < devchan; j++) {
			for (i = 0; i < PRES; i++) {
				tp.p[0] = mxade[j] * i/(PRES-1.0);
				dE762dE94[j]->interp(dE762dE94[j], &tp);

				if (i == 0)
					start_line_g(&gg, tp.p[0], tp.v[0], cal_plot_colors[j % MXGPHS]);
				else
					extend_line_g(&gg, tp.p[0], tp.v[0]);
			}
		}
		printf("adE76 to rdE94 equivalence curve plot:\n");
		do_plot_g(&gg, 0.0, 0.0, 0.0, 0.0, 1.0, 0, 1);

		clear_g(&gg);
	}

	if ((initial || recal) && pct->devmaxset) {

		/* Compute an ideal power-like value for test target */
		for (j = 0; j < devchan; j++) {
			double hdv;			/* Half device value */
			double hdvrde;		/* Half device rDE value */
			double thdvrde;		/* Target half device rDE value */
			double thdv;		/* Target half device value */
			double fdvrde;		/* Full device rDE value */
			co tp;	/* Test point */
	
			/* full rDE */
			tp.p[0] = pct->devmax[j];
			rde[j]->interp(rde[j], &tp);
			fdvrde = tp.v[0];

			/* Half device value of maximum */
			hdv = pct->devmax[j] * 0.5;
	
			/* rDE value half the device value */
			tp.p[0] = hdv;
			rde[j]->interp(rde[j], &tp);
			hdvrde = tp.v[0];
	
			/* rDE value we'd like at half the device value */
			thdvrde = 0.5 * fdvrde;
			
			/* Device value to get the rDE value we'd like at half */
			if ((thdv = rspl_ilookup(rde[j], 0.5, thdvrde)) < 0.0)
				error("Unexpected failure to invert curve %d for ADE %f",j,thdvrde); 

//printf("hdv %f, hdvrde %f, thdvrde %f, fdvrde %f, thdv %f\n",hdv,hdvrde,thdvrde, fdvrde,thdv);
	
			/* Power like value needed to get rDE value we'd like at hald device */
			idpow[j] = icx_powlike_needed(hdv, thdv); 
			
		}
	}

	if (verb > 2) {
		printf("Abs DE values:\n");
		for (i = 0; i < PRES; i++) {
			co tp;	/* Test point */
			tp.p[0]= i/(double)(PRES-1);

			printf("  dev %f, aDE",tp.p[0]);
			for (j = 0; j < 6 && j < devchan; j++) {
				ade[j]->interp(ade[j], &tp);
				printf(" %f",tp.v[0]);
			}
			printf("\n");
		}
		printf("Rel DE values:\n");
		for (i = 0; i < PRES; i++) {
			co tp;	/* Test point */
			tp.p[0]= i/(double)(PRES-1);

			printf("  dev %f, rdev",tp.p[0]);
			for (j = 0; j < 6 && j < devchan; j++) {
				rde[j]->interp(rde[j], &tp);
				printf(" %f",tp.v[0]);
			}
			printf("\n");
		}
	}

	if (initial || recal) {
		if (verb && pct->is_set(pct))  {
			for (j = 0; j < devchan; j++) {
				printf("Chan %d Dev max %f, aDE Max %f, aDE Min %f\n",j,pct->devmax[j],pct->ademax[j],pct->ademin[j]);
			}
		}

		if (verb)  {
			double avgpow = 0.0;
			if (idpow[0] >= 0.0) {
				for (j = 0; j < devchan; j++) {
					printf("Chan %d ideal targen power = %f\n",j,idpow[j]);
					avgpow += idpow[j];
				}
				avgpow /= (double)devchan;
				printf("Average ideal targen power = %f\n",avgpow);
			} else {
				printf("No devmax, so can't compute ideal targen power.\n");
			}
		}
	}

	/* Plot both the delta E curves, and markers */
	if (doplot) {
		co tp;	/* Test point */
		double xx[PRES];
		double yy[MXGPHS][PRES];
		double cx[MXGPHS], cy[MXGPHS];
		int nmark;

		printf("Absolute dE76 plot:\n");

		for (i = 0; i < PRES; i++) {
			xx[i] = i/(double)(PRES-1);

			for (j = 0; j < 10 && j < devchan; j++) {
				tp.p[0] = xx[i];
				ade[j]->interp(ade[j], &tp);
				yy[j][i] = tp.v[0];
			}
		}
		nmark = 0;
		if (pct->is_set(pct)) {
			/* Add markers for deMax */
			for (j = 0; j < 10 && j < devchan; j++) {
				cx[j] = pct->devmax[j];
				cy[j] = pct->ademax[j];
				nmark++;
			}
		}
		do_plot10p(xx, devchan > 3 ? yy[3] : NULL,
		               devchan > 1 ? yy[1] : NULL,
		               devchan > 4 ? yy[4] : NULL,
		               devchan > 0 ? yy[0] : NULL,
		               devchan > 2 ? yy[2] : NULL,
		               devchan > 5 ? yy[5] : NULL,
		               devchan > 6 ? yy[6] : NULL,
		               devchan > 7 ? yy[7] : NULL,
		               devchan > 8 ? yy[8] : NULL,
		               devchan > 9 ? yy[9] : NULL,
		               PRES,
					   cx, cy, verify ? 0 : nmark);

		printf("Relative dE94 plot:\n");

		for (i = 0; i < PRES; i++) {
			xx[i] = i/(double)(PRES-1.0);

			for (j = 0; j < 10 && j < devchan; j++) {
				tp.p[0] = xx[i];
				rde[j]->interp(rde[j], &tp);
				yy[j][i] = tp.v[0];
			}
		}
		nmark = 0;
		if (pct->is_set(pct)) {
			/* Add markers for deMax */
			for (j = 0; j < 10 && j < devchan; j++) {
				cx[j] = pct->devmax[j];
				tp.p[0] = cx[j];
				rde[j]->interp(rde[j], &tp);
				cy[j] = tp.v[0];
				nmark++;
			}
		}
		do_plot10p(xx, devchan > 3 ? yy[3] : NULL,
		             devchan > 1 ? yy[1] : NULL,
		             devchan > 4 ? yy[4] : NULL,
		             devchan > 0 ? yy[0] : NULL,
		             devchan > 2 ? yy[2] : NULL,
		             devchan > 5 ? yy[5] : NULL,
		             devchan > 6 ? yy[6] : NULL,
		             devchan > 7 ? yy[7] : NULL,
		             devchan > 8 ? yy[8] : NULL,
		             devchan > 9 ? yy[9] : NULL,
		             PRES,
					 cx, cy, verify ? 0 : nmark);

		if (doplot >= 2 && idpow[0] > 0.0) {
			printf("Relative dE94 plot with ideal targen power applied:\n");

			for (i = 0; i < PRES; i++) {
				xx[i] = i/(double)(PRES-1.0);
	
				for (j = 0; j < 10 && j < devchan; j++) {
					tp.p[0] = icx_powlike(xx[i],idpow[j]);
					rde[j]->interp(rde[j], &tp);
					yy[j][i] = tp.v[0];
				}
			}
			nmark = 0;
			if (pct->is_set(pct)) {
				/* Add markers for deMax */
				for (j = 0; j < 10 && j < devchan; j++) {
					cx[j] = pct->devmax[j];
					tp.p[0] = icx_powlike(cx[j],idpow[j]);
					rde[j]->interp(rde[j], &tp);
					cy[j] = tp.v[0];
					nmark++;
				}
			}
			do_plot10p(xx, devchan > 3 ? yy[3] : NULL,
			             devchan > 1 ? yy[1] : NULL,
			             devchan > 4 ? yy[4] : NULL,
			             devchan > 0 ? yy[0] : NULL,
			             devchan > 2 ? yy[2] : NULL,
			             devchan > 5 ? yy[5] : NULL,
			             devchan > 6 ? yy[6] : NULL,
			             devchan > 7 ? yy[7] : NULL,
			             devchan > 8 ? yy[8] : NULL,
			             devchan > 9 ? yy[9] : NULL,
			             PRES,
						 cx, cy, verify ? 0 : nmark);
		}
	}

	/* Compare the previous expected aDE against the current one */
	if (verify) {
		co tp;	/* Test point */
		double avg[MAX_CHAN];
		double max[MAX_CHAN];
		double rms[MAX_CHAN];

		/* Verify each channel */
		for (j = 0; j < devchan; j++) {
			co tp;

			avg[j] = 0.0;
			max[j] = -1.0;
			rms[j] = 0.0;

			/* Sample it at GRES */
			for (i = 0; i < GRES; i++) {
				double iv, targ, val, tt;

				iv = i/(GRES-1.0);

				/* Lookup the ade that we expect */ 
				tp.p[0] = iv;
				pcade[j]->interp(pcade[j], &tp);
				targ = tp.v[0];

				/* Lookup the ade that we have */ 
				tp.p[0] = iv;
				ade[j]->interp(ade[j], &tp);
				val = tp.v[0];
				val += pct->ademin[j];

				/* Compute the stats */
				tt = fabs(targ - val);
				avg[j] += tt;
				rms[j] += tt * tt;
				if (tt > max[j])
					max[j] = tt;
//printf("~1 chan %d, ix %d, iv %f, targ %f, actual %f, err %f\n",j,i,iv,targ,val,tt);
			}
			avg[j] /= (double)GRES;
			rms[j] /= (double)GRES;
			rms[j] = sqrt(rms[j]);
			if (max[j] > ver_maxde)
				verified = 0;
		}
		if (verb) {
			for (j = 0; j < devchan; j++) {
				printf("Verify results:\n");
				printf("Channel %d has DE avg %.1f, rms %.1f, max %.1f\n",j,avg[j],rms[j],max[j]);
			}
			if (verified)
				printf("Verified OK\n");
			else
				printf("Verification FAILED\n");
		}

		/* Plot the verification curves */
		if (doplot) {
			double xx[PRES];
			double yy[MXGPHS][PRES];
	
			printf("Verification match plot:\n");

			for (j = 0; j < 10 && j < devchan; j++) {
				co tp;	/* Test point */
				double max;

				/* Establish the scale */
				tp.p[0] = 1.0;
				pcade[j]->interp(pcade[j], &tp);
				max = tp.v[0] - pct->ademin[j];;

				for (i = 0; i < PRES; i++) {
					xx[i] = i/(double)(PRES-1);

					/* Convert ade target to original device */
					tp.v[0] = max * xx[i] + pct->ademin[j];
					if ((tp.p[0] = rspl_ilookup(pcade[j], 1.0, tp.v[0])) < 0.0)
						error("Unexpected failure to invert curve %d for pcADE %f",j,tp.v[0]); 

					/* Convert device to current device ade */
					ade[j]->interp(ade[j], &tp);
					if (fabs(max) > 0.1)
						yy[j][i] = tp.v[0]/max;
					else
						yy[j][i] = 0.0;
				}
			}
			do_plot10(xx, devchan > 3 ? yy[3] : NULL,
						  devchan > 1 ? yy[1] : NULL,
						  devchan > 4 ? yy[4] : NULL,
						  devchan > 0 ? yy[0] : NULL,
						  devchan > 2 ? yy[2] : NULL,
						  devchan > 5 ? yy[5] : NULL,
		                  devchan > 6 ? yy[6] : NULL,
		                  devchan > 7 ? yy[7] : NULL,
		                  devchan > 8 ? yy[8] : NULL,
		                  devchan > 9 ? yy[9] : NULL,
			              PRES, 0);
		}
		/* End verify */

	} else if (initial) {

		/* Convert any transfer curve target points into a smooth curve */ 
		if (pct->no_tpoints > 0) {
			int gres[MXDI] = { GRES };
			co *pnts;
		
			if ((pnts = (co *)calloc(pct->no_tpoints + 2, sizeof(co))) == NULL)
				error ("Malloc of rspl points failed");
	
			for (j = 0; j < devchan; j++) {
				int npts;
				int gotmin, gotmax;
	
				/* Count the number of valid points */
				for (npts = i = 0; i < pct->no_tpoints; i++) {
					if (pct->tpoints[i].val[j] >= 0.0)
						npts++;
				}
				if (npts == 0)
					continue;		/* No target curve for this channel */
	
				if ((tcurves[j] = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL)
					error("new_rspl(1,1) failed");
				
				gotmin = gotmax = 0;
				for (npts = i = 0; i < pct->no_tpoints; i++) {
					if (pct->tpoints[i].val[j] < 0.0)
						continue;
	
					pnts[npts].p[0] = pct->tpoints[i].loc;
					pnts[npts].v[0] = pct->tpoints[i].val[j];
					if (pnts[npts].p[0] < 0.0)
						pnts[npts].p[0] = 0.0;
					else if (pnts[npts].p[0] > 1.0)
						pnts[npts].p[0] = 1.0;
					if (pnts[npts].v[0] < 0.0)
						pnts[npts].v[0] = 0.0;
					else if (pnts[npts].v[0] > 1.0)
						pnts[npts].v[0] = 1.0;
	
					if (pnts[npts].p[0] <= 0.05)
						gotmin = 1;
					if (pnts[npts].p[0] >= 0.95)
						gotmax = 1;
					npts++;
				}
				/* Add default anchors if there are none supplied */
				if (gotmin == 0) {
					pnts[npts].p[0] = 0.0;
					pnts[npts++].v[0] = 0.0;
				}
				if (gotmax == 0) {
					pnts[npts].p[0] = 1.0;
					pnts[npts++].v[0] = 1.0;
				}
	
				/* Fit the curve to the given points */
				tcurves[j]->fit_rspl(tcurves[j], RSPLFLAGS, pnts, npts,
				                NULL, NULL, gres, NULL, NULL, TCURVESMOOTH, NULL, NULL); 
			}
			free(pnts);
	
			/* Plot the target curves */
			if (doplot) {
				double xx[PRES];
				double yy[MXGPHS][PRES];
	
				printf("Relative dE94 target curves plot:\n");
	
				for (i = 0; i < PRES; i++) {
					co tp;	/* Test point */
					double pp = i/(PRES-1.0);
	
					xx[i] = pp;
					for (j = 0; j < 10 && j < devchan; j++) {
						if (tcurves[j] != NULL) {
							tp.p[0] = pp;
							tcurves[j]->interp(tcurves[j], &tp);
							yy[j][i] = tp.v[0];
						} else
							yy[j][i] = pp;
					}
				}
				do_plot10(xx, devchan > 3 ? yy[3] : NULL,
							  devchan > 1 ? yy[1] : NULL,
							  devchan > 4 ? yy[4] : NULL,
							  devchan > 0 ? yy[0] : NULL,
							  devchan > 2 ? yy[2] : NULL,
							  devchan > 5 ? yy[5] : NULL,
		                      devchan > 6 ? yy[6] : NULL,
		                      devchan > 7 ? yy[7] : NULL,
		                      devchan > 8 ? yy[8] : NULL,
		                      devchan > 9 ? yy[9] : NULL,
				              PRES, 0);
			}
		}

		/* Do inverse lookup to create relative dE94 linearization curves */
		n_cvals = cal_res;
		for (j = 0; j < devchan; j++) {
			co tp;
			double rdemax, rdemin;	/* Relative DE max and min target */

			/* Convert absolute dE max aim to relative dE aim */
			if ((rdemax = rspl_ilookup(ade[j], 0.0, pct->ademax[j])) < 0.0)
				error("Unexpected failure to invert curve %d for DE %f",j,pct->ademax[j]); 
			tp.p[0] = rdemax;
			rde[j]->interp(rde[j], &tp);
			rdemax = tp.v[0];

			/* Convert absolute dE min aim to relative dE aim */
			if ((rdemin = rspl_ilookup(ade[j], 1.0, pct->ademin[j])) < 0.0)
				error("Unexpected failure to invert curve %d for DE %f",j,pct->ademax[j]); 
			tp.p[0] = rdemin;
			rde[j]->interp(rde[j], &tp);
			rdemin = tp.v[0];

			if (verb > 0)
				printf("Chan %d: rDE Max = %f, rDE Min = %f\n",j,rdemax,rdemin);

			if ((cvals[j] = (wval *)malloc(sizeof(wval) * n_cvals)) == NULL)
				error("Malloc of %d cvals failed",n_cvals);

			/* Convert relative delta E aim to device value */
			for (i = 0; i < n_cvals; i++) {
				double x = i/(n_cvals-1.0);
				double inv;

				cvals[j][i].inv = x;

				/* Apply any aim tweak curve */
				if (tcurves[j] != NULL) {
					tp.p[0] = x;
					tcurves[j]->interp(tcurves[j], &tp);
					x = tp.v[0];
				}

				inv = x * (rdemax -rdemin) + rdemin;

				if ((cvals[j][i].dev = rspl_ilookup(rde[j], 0.5, inv)) < 0.0)
					error("Unexpected failure to invert curve %d for DE %f",j,inv); 
//printf("~1 chan %d, step %d, inv %f, detarg %f, got dev %f\n",j,i,x,pp[0].v[0],pp[k].p[0]);
			}
		}
		/* End initial */

	} else if (recal || imitate) {

		/* Lookup the expected adE76 for each input device value, and */
		/* then do inverse lookup in measured adE76 to create calibration */
		/* to previous/imitation adE76 target */
		n_cvals = cal_res;
		for (j = 0; j < devchan; j++) {
			co tp;

			if ((cvals[j] = (wval *)malloc(sizeof(wval) * n_cvals)) == NULL)
				error("Malloc of %d cvals failed",n_cvals);

			for (i = 0; i < n_cvals; i++) {
				double x = i/(n_cvals-1.0);

				cvals[j][i].inv = tp.p[0] = x;
				pcade[j]->interp(pcade[j], &tp);

				if ((cvals[j][i].dev = rspl_ilookup(ade[j], 0.5, tp.v[0])) < 0.0)
					error("Unexpected failure to invert curve %d for DE %f",j,tp.v[0]); 
//printf("~1 chan %d, ix %d, inv %f, pcade %f, iade %f\n",j,i,x,tp.v[0],cvals[j][i].dev);
			}
		}
		/* End recal or imitate */
	}

	if (initial || recal || imitate) {

		if (verb > 2) {
			printf("Calibration curve values:\n");
			for (i = 0; i < n_cvals; i++) {
				printf("  inv %f, dev",cvals[0][i].inv);
				for (j = 0; j < devchan; j++) {
					printf(" %f",cvals[j][i].dev);
				}
				printf("\n");
			}
		}

		/* Plot the calibration curves */
		if (doplot) {
			double xx[PRES];
			double yy[MXGPHS][PRES];

			printf("Calibration curve plot:\n");

			for (i = 0; i < PRES; i++) {
				xx[i] = i/(PRES-1.0);
				for (j = 0; j < 10 && j < devchan; j++) {
					yy[j][i] = interp_cal(cvals[j], n_cvals, xx[i]);
				}
			}
			do_plot10(xx, devchan > 3 ? yy[3] : NULL,	/* Black */
			              devchan > 1 ? yy[1] : NULL,	/* Red */
			              devchan > 4 ? yy[4] : NULL,	/* Green */
			              devchan > 0 ? yy[0] : NULL,	/* Blue */
			              devchan > 2 ? yy[2] : NULL,	/* Yellow */
			              devchan > 5 ? yy[5] : NULL,	/* Purple */
		                  devchan > 6 ? yy[6] : NULL,	/* Brown */
		                  devchan > 7 ? yy[7] : NULL,	/* Orange */
		                  devchan > 8 ? yy[8] : NULL,	/* Grey */
		                  devchan > 9 ? yy[9] : NULL,	/* White */
			              PRES, 0);
		}

	}

	/* Compute and plot the target and expected relative dE94 curves */
	if (doplot
	) {
		double xx1[PRES];
		double yy1[MAX_CHAN][PRES];
		double xx2[PRES];
		double yy2[MAX_CHAN][PRES];

		/* Compute the target calibrated relative deltaE '94 */
		if (initial) {

			/* Target is linear rdE with tcurves tweaks amd scaled by min & max. */
			for (j = 0; j < devchan; j++) {
				co tp;
				double rdemin, rdemax;	/* Relative DE min and max targets */
	
				/* Convert absolute dE max aim to relative dE aim */
				if ((rdemax = rspl_ilookup(ade[j], 0.0, pct->ademax[j])) < 0.0)
					error("Unexpected failure to invert curve %d for DE %f",j,pct->ademax[j]); 
	
				tp.p[0] = rdemax;
				rde[j]->interp(rde[j], &tp);
				rdemax = tp.v[0];
	
				if ((rdemin = rspl_ilookup(ade[j], 1.0, pct->ademin[j])) < 0.0)
					error("Unexpected failure to invert curve %d for DE %f",j,pct->ademax[j]); 
	
				tp.p[0] = rdemin;
				rde[j]->interp(rde[j], &tp);
				rdemin = tp.v[0];
	
				/* Convert relative delta E aim to device value */
				for (i = 0; i < PRES; i++) {
					double x = i/(PRES-1.0);
					double inv;
	
					xx1[i] = x;
	
					/* Apply any aim tweak curve */
					if (tcurves[j] != NULL) {
						tp.p[0] = x;
						tcurves[j]->interp(tcurves[j], &tp);
						x = tp.v[0];
					}
	
					yy1[j][i] = x * (rdemax - rdemin) + rdemin;
				}
			}

		} else {

			/* Equivalent rdE94 target is pcade[] converted through dE762dE94 */
			for (j = 0; j < devchan; j++) {
				co tp;
	
				for (i = 0; i < PRES; i++) {
					double x = i/(PRES-1.0);
	
					xx1[i] = x;
	
					tp.p[0] = x;
					pcade[j]->interp(pcade[j], &tp);
					tp.p[0] = tp.v[0];
					dE762dE94[j]->interp(dE762dE94[j], &tp);
					yy1[j][i] = tp.v[0];
				}
			}

		}

		/* Plot the expected calibrated relative deltaE '94 */

		if (initial) {

			/* Compute rdE94 of the calibrated response from this measurement */
			for (j = 0; j < devchan; j++) {
				double prev[3], tot = 0.0;

				for (i = 0; i < PRES; i++) {
					double x = i/(PRES-1.0);
					co tp;	/* Test point */

					xx2[i] = x;

					tp.p[0] = interp_cal(cvals[j], n_cvals, x);	/* Calibrated device value */
					raw[j]->interp(raw[j], &tp);	/* Expected L*b*a response */

					if (i == 0) {
						prev[0] = tp.v[0];
						prev[1] = tp.v[1];
						prev[2] = tp.v[2];
						if (tp.p[0] > 0) {	/* Hmm. */
							rde[j]->interp(rde[j], &tp);	/* Initial relative dE94 */
							tot = tp.v[0];
						}

					} else {
						tot += icmCIE94(tp.v, prev);
						prev[0] = tp.v[0];
						prev[1] = tp.v[1];
						prev[2] = tp.v[2];
					}
					yy2[j][i] = tot; 
				}
			}

			/* Comute the adE76 of the raw response from this measurement, */
			/* and convert this to the equivalent rdE94 */

		} else {	/* recal, verify or imitate */
			/* Comute the adE76 of the calibrated response from this measurement, */
			/* and convert this to the equivalent rdE94 */

			for (j = 0; j < devchan; j++) {
				double wh[3];

				for (i = 0; i < PRES; i++) {
					double x = i/(PRES-1.0);
					co tp;	/* Test point */

					xx2[i] = x;

					if (verify)		/* raw==pre-calibrated device value */
						tp.p[0] = x;
					else			/* Lookup calibrated device value */
						tp.p[0] = interp_cal(cvals[j], n_cvals, x);
					raw[j]->interp(raw[j], &tp);	/* Expected L*a*b* response */

					if (i == 0) {
						wh[0] = tp.v[0];
						wh[1] = tp.v[1];
						wh[2] = tp.v[2];
					}
					tp.p[0] = icmLabDE(tp.v, wh);		/* adE76 */
					tp.p[0] += pct->ademin[j]; 

					dE762dE94[j]->interp(dE762dE94[j], &tp);
					yy2[j][i] = tp.v[0];
				}
			}
		}

		if (doplot) {
			plot_g gg = { 0 };

			for (j = 0; j < devchan; j++) { 
				float rgb[3];
 
				/* Make targets red, so they show when not covered by expected */
				rgb[0] = 1.0;
				rgb[1] = 0.0;
				rgb[2] = 0.0;
				if ((j % MXGPHS) == 1		/* Except redish colors */
				 || (j % MXGPHS) == 6
				 || (j % MXGPHS) == 7
				 || (j % MXGPHS) == 9
				 || (j % MXGPHS) == 11
				 || (j % MXGPHS) == 13
				 || (j % MXGPHS) == 15) {
					rgb[0] = 0.0;		/* where we make it blue */
					rgb[1] = 0.0;
					rgb[2] = 1.0;
				} 
				for (i = 0; i < PRES; i++) {
					if (i == 0)
						start_line_g(&gg, xx1[i], yy1[j][i], rgb);
					else
						extend_line_g(&gg, xx1[i], yy1[j][i]);
				}

				for (i = 0; i < PRES; i++) {
					if (i == 0)
						start_line_g(&gg, xx2[i], yy2[j][i], cal_plot_colors[j % MXGPHS]);
					else
						extend_line_g(&gg, xx2[i], yy2[j][i]);
				}
			}

			printf("Relative dE94 Target vs. Expected plots:\n");
			do_plot_g(&gg, 0.0, 0.0, 0.0, 0.0, 1.0, 0, 1);

			clear_g(&gg);
		}
	}

	if (initial || recal || imitate) {

		/* Create 3D plot of calibrated colorant loci */
		if (do3dplot) {
			char vname[MAXNAMEL+3] = "";
			vrml *wrl;
			int doaxes = 0;
			int devres = 21;
	
			strcpy(vname, baname);
			strcat(vname, "_c");
	
			if ((wrl = new_vrml(vname, doaxes, vrml_lab)) == NULL)
				error("new_vrml failed for '%s%s'",vname,vrml_ext());
	
			for (j = 0; j < devchan; j++) {
				double last[3];

				for (i = 0; i < devres; i++) {
					co tp;	/* Test point */
					double x;

					x = i/(double)(devres-1);
					tp.p[0] = interp_cal(cvals[j], n_cvals, x);
					raw[j]->interp(raw[j], &tp);
					if (i > 0) {
						wrl->add_cone(wrl, last, tp.v, NULL, 20/(devres-1.0));
					}
					icmCpy3(last, tp.v);
				}
			}
			wrl->del(wrl);		/* Write file and delete */
		}

		/* Write out an Argyll .CAL file */
		if (dowrite) {
			cgats *ocg;						/* output cgats structure */
			time_t clk = time(0);
			struct tm *tsp = localtime(&clk);
			char *atm = asctime(tsp);		/* Ascii time */
			char *ident = icx_inkmask2char(devmask, 1); 
			char *bident = icx_inkmask2char(devmask, 0); 
			cgats_set_elem *setel;			/* Array of set value elements */
			int nsetel = 0;
			int ncps;						/* Number of curve parameters */
			double *cps[3];					/* Arrays of curve parameters */
			char *bp = NULL, buf[100];		/* Buffer to sprintf into */
			co tp;

			ocg = new_cgats();				/* Create a CGATS structure */
			ocg->add_other(ocg, "CAL"); 	/* our special type is Calibration file */

			ocg->add_table(ocg, tt_other, 0);	/* Add a table for RAMDAC values */
			ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Device Calibration Curves",NULL);
			ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll printcal", NULL);
			atm[strlen(atm)-1] = '\000';	/* Remove \n from end */
			ocg->add_kword(ocg, 0, "CREATED",atm, NULL);

			ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL);
			ocg->add_kword(ocg, 0, "COLOR_REP", ident, NULL);

			if (xpi.deviceMfgDesc != NULL)
				ocg->add_kword(ocg, 0, "MANUFACTURER", xpi.deviceMfgDesc, NULL);
			if (xpi.modelDesc != NULL)
				ocg->add_kword(ocg, 0, "MODEL", xpi.modelDesc, NULL);
			if (xpi.profDesc != NULL)
				ocg->add_kword(ocg, 0, "DESCRIPTION", xpi.profDesc, NULL);
			if (xpi.copyright != NULL)
				ocg->add_kword(ocg, 0, "COPYRIGHT", xpi.copyright, NULL);

			/* Setup the table which holds the translation from calibrated */
			/* device value "I" to the raw device channel value */ 
			sprintf(buf, "%s_I",bident);
			ocg->add_field(ocg, 0, buf, r_t);
			nsetel++;
			for (j = 0; j < devchan; j++) {
				inkmask imask = icx_index2ink(devmask, j);
				sprintf(buf, "%s_%s",bident,icx_ink2char(imask));
				ocg->add_field(ocg, 0, buf, r_t);
				nsetel++;
			}
			if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
				error("Malloc failed!");

			/* Write the per channel device to device loolup curve values */
			if (devmask & ICX_ADDITIVE) {
				for (i = n_cvals-1; i >= 0; i--) {
		
					setel[0].d = 1.0 - cvals[0][i].inv;
					for (j = 0; j < devchan; j++)
						setel[1+j].d = 1.0 - cvals[j][i].dev;
					ocg->add_setarr(ocg, 0, setel);
				}
			} else {
				for (i = 0; i < n_cvals; i++) {
		
					setel[0].d = cvals[0][i].inv;
					for (j = 0; j < devchan; j++)
						setel[1+j].d = cvals[j][i].dev;
					ocg->add_setarr(ocg, 0, setel);
				}
			}

			free(setel);

			/* Add a second table with the adE76 calibration target information. */
			if (pct->write(pct, ocg, 1) != 0)
				error("Writing cal target info to cal file '%s'",outname);

			/* Add a third table which is the expected absolute DE response */
			/* of the calibrated device. */
			ocg->add_table(ocg, tt_other, 0);	/* Add a table for RAMDAC values */
			ocg->add_kword(ocg, 2, "DESCRIPTOR", "Argyll Output Calibration Expected Absolute DE Response",NULL);
			ocg->add_kword(ocg, 2, "ORIGINATOR", "Argyll printcal", NULL);
			atm[strlen(atm)-1] = '\000';	/* Remove \n from end */
			ocg->add_kword(ocg, 2, "CREATED",atm, NULL);

			ocg->add_kword(ocg, 2, "DEVICE_CLASS","OUTPUT", NULL);
			ocg->add_kword(ocg, 2, "COLOR_REP", ident, NULL);

			/* Setup the table which holds the translation from calibrated */
			/* device value "I" to the expected absolute deltaE value for */
			/* each colorant. */
			sprintf(buf, "%s_I_DE",bident);
			ocg->add_field(ocg, 2, buf, r_t);
			nsetel++;
			for (j = 0; j < devchan; j++) {
				inkmask imask = icx_index2ink(devmask, j);
				sprintf(buf, "%s_%s_DE",bident,icx_ink2char(imask));
				ocg->add_field(ocg, 2, buf, r_t);
				nsetel++;
			}
			if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
				error("Malloc failed!");

			if (initial) {
				/* Compute expected calibration adE76 curves */
				for (i = 0; i < n_cvals; i++) {
					int ix = i;
	
					/* Calibrated device value */
					if (devmask & ICX_ADDITIVE) {
						ix = n_cvals -1 - i;
						setel[0].d = 1.0 - cvals[0][ix].inv;
					} else
						setel[0].d = cvals[0][ix].inv;
	
					for (j = 0; j < devchan; j++) {
						tp.p[0] = cvals[j][ix].dev;		/* Calibrated device value */
						ade[j]->interp(ade[j], &tp);	/* Corresponding ade value */
						setel[1+j].d = tp.v[0];
					}
					ocg->add_setarr(ocg, 2, setel);
				}

			} else {

				/* Copy out original expected calibration adE76 curves pcade, for consistency.. */
				for (i = 0; i < n_cvals; i++) {
					int ix = i;
					double inv = i/(n_cvals-1.0);

					setel[0].d = inv;
	
					for (j = 0; j < devchan; j++) {
						tp.p[0] = inv;
						pcade[j]->interp(pcade[j], &tp);	/* Corresponding ade value */
						setel[1+j].d = tp.v[0];
					}
					ocg->add_setarr(ocg, 2, setel);
				}

			}

			free(setel);

			if (dE762dE94[0] != NULL) {
				double inmax[MAX_CHAN];

				/* Add a fourth table which is the original adE76 to rdE94 equivalence */
				/* of the calibrated device. */
				ocg->add_other(ocg, "CALDEEQ"); 	/* our special type is calibration dE equivalence */
				ocg->add_table(ocg, tt_other, 1);	/* Add a table for values */
				ocg->add_kword(ocg, 3, "DESCRIPTOR", "Argyll Output Calibration adE76 to rdE94 equivalence",NULL);
				ocg->add_kword(ocg, 3, "ORIGINATOR", "Argyll printcal", NULL);
				atm[strlen(atm)-1] = '\000';	/* Remove \n from end */
				ocg->add_kword(ocg, 3, "CREATED",atm, NULL);

				ocg->add_kword(ocg, 3, "DEVICE_CLASS","OUTPUT", NULL);
				ocg->add_kword(ocg, 3, "COLOR_REP", ident, NULL);

				ocg->add_field(ocg, 3, "ID", i_t);
				nsetel++;
				for (j = 0; j < devchan; j++) {
					inkmask imask = icx_index2ink(devmask, j);
					sprintf(buf, "%s_%s_ADE",bident,icx_ink2char(imask));
					ocg->add_field(ocg, 3, buf, r_t);
					nsetel++;
					sprintf(buf, "%s_%s_RDE",bident,icx_ink2char(imask));
					ocg->add_field(ocg, 3, buf, r_t);
					nsetel++;
				}
				if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
					error("Malloc failed!");

				for (i = 0; i < n_cvals; i++) {
					int ix = i;

					setel[0].i = i+1;

					for (j = 0; j < devchan; j++) {
						double ival = mxade[j] * i/(n_cvals-1.0);

						/* adE76 value */
						setel[1 + 2 * j + 0].d = ival;

						tp.p[0] = ival;
						dE762dE94[j]->interp(dE762dE94[j], &tp);	/* Corresponding rdE94 value */
						setel[1 + 2 * j + 1].d = tp.v[0];
					}
					ocg->add_setarr(ocg, 3, setel);
				}

				free(setel);
			}

			if (ocg->write_name(ocg, outname))
				error("Write error to file '%s': %s",outname,ocg->e.m);

			if (verb)
				printf("Written calibration file '%s'\n",outname);

			ocg->del(ocg);		/* Clean up */
			free(ident);
			free(bident);
		}

		/* 
			The structure of *.AMP is very simple.
	
				It has 5 tables that have
					256 entries that are 8-bit (byte), even for 16 bit mode.
			
						The first table 0000..00FFh is the CMYK channel.
						The second table 0100..01FFh is the C channel.
						The third table 0200..02FFh is the M channel.
						The fourth table 0300..03FFh is the Y channel.
						The fifth table 0300..03FFh is the K channel.
			
						Table position nn00h is the black end and table position nnFFh
						is the white end.
			
		*/

		/* Write an Adobe map format (.AMP) file */
		/* (It's not clear if more than 4 channels is allowed) */
		if (dowrite && doamp) {
			FILE *fp;
			cgatsFile *p;
			char nmode[50] = { '\000' };

			strcpy(nmode, "w");
#if defined(O_BINARY) || defined(_O_BINARY)
			strcat(nmode, "b");
#endif
			if ((fp = fopen(ampname, nmode)) == NULL)
				error("Couldn't open '%s' for writing",ampname);
			
			/* CMYK table is unity */
			for (i = 0; i < 256; i++) {
				if (putc(i,fp) == EOF)
					error("Error writing to fle '%s'",ampname);
			}
			for (j = 0; j < devchan; j++) {
				for (i = 0; i < 256; i++) {
					int x;
					if (devmask & ICX_ADDITIVE)
						x = (int)(cvals[j][i].dev * 255.0 + 0.5);		/* ??? */
					else
						x = 255 - (int)(cvals[j][255 - i].dev * 255.0 + 0.5);
					if (putc(x,fp) == EOF)
						error("Error writing to fle '%s'",ampname);
//printf("~1 chan %d, inv %d, dev %d\n",j,i,x);
				}
			}
			/* Extra 1:1 table */
			for (i = 0; i < 256; i++) {
				if (putc(i,fp) == EOF)
					error("Error writing to fle '%s'",ampname);
			}

			if (fclose(fp) != 0)
				error("Closing '%s' failed",ampname);

			if (verb)
				printf("Written calibration curves to '%s'\n",ampname);
		}
	}

	/* Free up various possible allocations */
	for (j = 0; j < devchan; j++) {
		if (pvals[j] != NULL)
			free(pvals[j]);
		if (raw[j] != NULL)
			raw[j]->del(raw[j]);
		if (ade[j] != NULL)
			ade[j]->del(ade[j]);
		if (rde[j] != NULL)
			rde[j]->del(rde[j]);
		if (dE762dE94[j] != NULL)
			dE762dE94[j]->del(dE762dE94[j]);
		if (pcade[j] != NULL)
			pcade[j]->del(pcade[j]);
		if (cvals[j] != NULL)
			free(cvals[j]);
		if (tcurves[j] != NULL)
			tcurves[j]->del(tcurves[j]);
	}

	if (!verified)
		return 1;
	return 0;
}












