

/* 
 * Argyll Color Correction System
 * Common display patch reading support.
 *
 * Author: Graeme W. Gill
 * Date:   2/11/2005
 *
 * Copyright 1998 - 2013 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.
 */

/* 
	TTBD:

		If verb, report white/black drift

		Should add option to ask for a black cal every N readings, for spectro's
 */

#ifdef __MINGW32__
# define WINVER 0x0500
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <time.h>
#include <string.h>
#include "copyright.h"
#include "aconfig.h"
#include "numlib.h"
#include "xspect.h"
#include "insttypes.h"
#include "conv.h"
#include "icoms.h"
#include "inst.h"
#include "spyd2.h"
#include "dispwin.h"
#include "dispsup.h"
#include "webwin.h"
#include "instappsup.h"

#undef SIMPLE_MODEL		/* Make fake device well behaved */
						/* else has offsets, quantization, noise etc. */

#define DRIFT_IPERIOD	40	/* Number of samples between drift interpolation measurements */
#define DRIFT_EPERIOD	20	/* Number of samples between drift extrapolation measurements */
//#define DRIFT_IPERIOD	6	/* Test values */
//#define DRIFT_EPERIOD	3

#ifdef SIMPLE_MODEL
# undef FAKE_NOISE			/* Add noise to _fake_ devices XYZ */
# undef FAKE_BITS 			/* Number of bits of significance of fake device */
#else
# define FAKE_NOISE 0.01		/* Add noise to _fake_ devices XYZ */
# define FAKE_BITS 9			/* Number of bits of significance of fake device */
#endif


/* -------------------------------------------------------- */
/* A default callback that can be provided as an argument to */
/* inst_handle_calibrate() to handle the display part of a */
/* calibration callback. */
/* Call this again with calc = inst_calc_none to cleanup afterwards. */
inst_code setup_display_calibrate(
	inst *p,
	inst_cal_cond calc,		/* Current condition. inst_calc_none for not setup */
	disp_win_info *dwi		/* Information to be able to open a display test patch */
) {
	inst_code rv = inst_ok, ev;
	dispwin *dw;	/* Display window to display test patches on, NULL if none. */
	a1logd(p->log,1,"setup_display_calibrate called\n");

	switch (calc) {
		case inst_calc_none:		/* Use this as a cleanup flag */
			if (dwi->dw == NULL && dwi->_dw != NULL) {
				dwi->_dw->del(dwi->_dw);
				dwi->_dw = NULL;
			}
			break;

		case inst_calc_emis_white:
			if (dwi->dw == NULL) {
				if (dwi->webdisp != 0) {
					if ((dwi->_dw = new_webwin(dwi->webdisp, dwi->hpatsize, dwi->vpatsize,
					                      dwi->ho, dwi->vo, 0, dwi->blackbg,
					                      p->log->verb, p->log->debug)) == NULL) {
						a1logd(p->log,1,"inst_handle_calibrate failed to create test window 0x%x\n",inst_other_error);
						return inst_other_error; 
					}
				} else {
					if ((dwi->_dw = new_dispwin(dwi->disp, dwi->hpatsize, dwi->vpatsize,
					                      dwi->ho, dwi->vo, 0, 0, NULL, dwi->blackbg,
					                      dwi->override, p->log->debug)) == NULL) {
						a1logd(p->log,1,"inst_handle_calibrate failed to create test window 0x%x\n",inst_other_error);
						return inst_other_error; 
					}
				}
				printf("Frequency calibration, Place instrument on test window.\n");
				printf(" Hit any key to continue,\n"); 
				printf(" or hit Esc or Q to abort:"); 
			} else {
				dwi->_dw = dwi->dw;
			}
			p->cal_gy_level = 1.0;
			dwi->_dw->set_color(dwi->_dw, 1.0, 1.0, 1.0);
			break;

		case inst_calc_emis_grey:
		case inst_calc_emis_grey_darker: 
		case inst_calc_emis_grey_ligher:
			if (dwi->dw == NULL) {
				if (dwi->webdisp != 0) {
					if ((dwi->_dw = new_webwin(dwi->webdisp, dwi->hpatsize, dwi->vpatsize,
					                      dwi->ho, dwi->vo, 0, dwi->blackbg,
					                      p->log->verb, p->log->debug)) == NULL) {
						a1logd(p->log,1,"inst_handle_calibrate failed to create test window 0x%x\n",inst_other_error);
						return inst_other_error; 
					}
				} else {
					if ((dwi->_dw = new_dispwin(dwi->disp, dwi->hpatsize, dwi->vpatsize,
					                      dwi->ho, dwi->vo, 0, 0, NULL, dwi->blackbg,
					                      dwi->override, p->log->debug)) == NULL) {
						a1logd(p->log,1,"inst_handle_calibrate failed to create test window 0x%x\n",inst_other_error);
						return inst_other_error; 
					}
				}
				printf("Cell ratio calibration, Place instrument on test window.\n");
				printf(" Hit any key to continue,\n"); 
				printf(" or hit Esc or Q to abort:"); 
			} else {
				dwi->_dw = dwi->dw;
			}
			if (calc == inst_calc_emis_grey) {
				p->cal_gy_level = 0.6;
				p->cal_gy_count = 0;
			} else if (calc == inst_calc_emis_grey_darker) {
				p->cal_gy_level *= 0.7;
				p->cal_gy_count++;
			} else if (calc == inst_calc_emis_grey_ligher) {
				p->cal_gy_level *= 1.4;
				if (p->cal_gy_level > 1.0)
					p->cal_gy_level = 1.0;
				p->cal_gy_count++;
			}
			if (p->cal_gy_count > 4) {
				printf("Cell ratio calibration failed - too many tries at setting grey level.\n");
				a1logd(p->log,1,"inst_handle_calibrate too many tries at setting grey level 0x%x\n",inst_internal_error);
				return inst_internal_error; 
			}
			dwi->_dw->set_color(dwi->_dw, p->cal_gy_level, p->cal_gy_level, p->cal_gy_level);
			break;

		default:
			/* Something isn't being handled */
			a1logd(p->log,1,"inst_handle_calibrate unhandled calc case 0x%x, err 0x%x\n",calc,inst_internal_error);
			return inst_internal_error;
	}
	return inst_ok;
}

/* -------------------------------------------------------- */
/* User requested calibration of the display instrument */
/* Do all available non-deferrable calibrations */
/* Should add an argument to be able to select type of calibration, */
/* rather than guessing what the user wants ? */
int disprd_calibration(
icompath *ipath,		/* Instrument path to open, &icomFakeDevice == fake */
flow_control fc,		/* Serial flow control */
int dtype,				/* Display type selection character */
int docbid,				/* NZ to only allow cbid dtypes */
int tele,				/* NZ for tele mode, falls back to spot mode */
int nadaptive,			/* NZ for non-adaptive mode */ 
int noinitcal,			/* NZ to disable initial instrument calibration */
disppath *disp,			/* display to calibrate. */
int webdisp,			/* If nz, port number for web display */
int blackbg,			/* NZ if whole screen should be filled with black */
int override,			/* Override_redirect on X11 */
double hpatsize,		/* Size of dispwin */
double vpatsize,
double ho, double vo,	/* Position of dispwin */
a1log *log				/* Verb, debug & error log */
) {
	instType itype = instUnknown;
	inst *p = NULL;
	int c;
	inst_code rv;
	baud_rate br = baud_19200;
	disp_win_info dwi;
	inst_cal_type calt = inst_calt_needed;
	inst_mode  cap;
	inst2_capability cap2;
	inst3_capability cap3;
	inst_mode mode = 0;

	memset((void *)&dwi, 0, sizeof(disp_win_info));
	dwi.webdisp = webdisp; 
	dwi.disp = disp; 
	dwi.blackbg = blackbg;
	dwi.override = override;
	dwi.hpatsize = hpatsize;
	dwi.vpatsize = vpatsize;
	dwi.ho = ho;
	dwi.vo = vo;

	p->log = new_a1log_d(log);

	a1logv(log, 1, "Setting up the instrument\n");

	if ((p = new_inst(ipath, 0, log, DUIH_FUNC_AND_CONTEXT)) == NULL) {
		a1logd(log, 1, "new_inst failed\n");
		return -1;
	}

	/* Establish communications */
	if ((rv = p->init_coms(p, br, fc, 15.0)) != inst_ok) {
		a1logd(p->log, 1, "init_coms returned '%s' (%s)\n",
		       p->inst_interp_error(p, rv), p->interp_error(p, rv));
		p->del(p);
		return -1;
	}

	/* Initialise the instrument */
	if ((rv = p->init_inst(p)) != inst_ok) {
		a1logd(log,1,"init_inst returned '%s' (%s)\n",
		       p->inst_interp_error(p, rv), p->interp_error(p, rv));
		p->del(p);
		return -1;
	}

	itype = p->get_itype(p);			/* Actual type */
	p->capabilities(p, &cap, &cap2, &cap3);

	if (tele && !IMODETST(cap, inst_mode_emis_tele)) {
		printf("Want telephoto measurement capability but instrument doesn't support it\n");
		printf("so falling back to emissive spot mode.\n");
		tele = 0;
	}
	
	/* Set to emission mode to read a display */
	if (tele)
		mode = inst_mode_emis_tele;
	else
		mode = inst_mode_emis_spot;
	if (nadaptive)
		mode |= inst_mode_emis_nonadaptive;
	
	/* (We're assuming spectral doesn't affect calibration ?) */

	if ((rv = p->set_mode(p, mode)) != inst_ok) {
		a1logd(log,1,"Set_mode failed with '%s' (%s)\n",
		       p->inst_interp_error(p, rv), p->interp_error(p, rv));
		return -1;
	}
	p->capabilities(p, &cap, &cap2, &cap3);

	/* Set the display type */
	if (dtype != 0) {		/* Given selection character */
		if (cap2 & inst2_disptype) {
			int ix;
			if ((ix = inst_get_disptype_index(p, dtype, docbid)) < 0) {
				a1logd(log,1,"Display type selection '%c' is not valid for instrument\n",dtype);
				p->del(p);
				return -1;
			}
		
			if ((rv = p->set_disptype(p, ix)) != inst_ok) {
				a1logd(log,1,"Setting display type failed failed with '%s' (%s)\n",
				       p->inst_interp_error(p, rv), p->interp_error(p, rv));
				p->del(p);
				return -1;
			}
		} else
			printf("Display type ignored - instrument doesn't support display type\n");
	}

	/* Disable initial calibration of machine if selected */
	if (noinitcal != 0) {
		if ((rv = p->get_set_opt(p,inst_opt_noinitcalib, 0)) != inst_ok) {
			a1logd(log,1,"Setting no-initail calibrate failed with '%s' (%s)\n",
			       p->inst_interp_error(p, rv), p->interp_error(p, rv));
			printf("Disable initial-calibrate not supported\n");
		}
	}

	/* Do the calibration */
	rv = inst_handle_calibrate(p, inst_calt_available, inst_calc_none, setup_display_calibrate, &dwi);
	setup_display_calibrate(p,inst_calc_none, &dwi); 
	if (rv == inst_unsupported) {
		printf("No calibration available for instrument in this mode\n");
	} else if (rv != inst_ok) {	/* Abort or fatal error */
		printf("Calibrate failed with '%s' (%s)\n",
	       p->inst_interp_error(p, rv), p->interp_error(p, rv));
		p->del(p);
		return -1;
	}

	/* clean up */
	p->del(p);
	a1logv(log, 1, "Finished setting up the instrument\n");

	return 0;
}

/* Take a series of readings from the display - implementation */
/* Return nz on fail/abort */
/* 1 = user aborted */
/* 2 = instrument access failed */
/* 3 = window access failed */ 
/* 4 = user hit terminate key */
/* 5 = system error */
/* Use disprd_err() to interpret it */
static int disprd_read_imp(
	disprd *p,
	col *cols,		/* Array of patch colors to be tested */
	int npat, 		/* Number of patches to be tested */
	int spat,		/* Start patch index for "verb", 0 if not used */
	int tpat,		/* Total patch index for "verb", 0 if not used */
	int acr,		/* If nz, do automatic final carriage return */
	int noinc,		/* If nz, don't increment the count */
	int tc,			/* If nz, termination key */
	instClamping clamp	/* NZ if clamp XYZ/Lab to be +ve */
) {
	int j, rv;
	int patch;
	ipatch val;		/* Return value */
	int ch;			/* Character */
	int cal_type;
	char id[CALIDLEN];
	inst2_capability cap2;

	/* Setup the keyboard trigger to return our commands */
	inst_set_uih(0x0, 0xff,  DUIH_TRIG);
	inst_set_uih('q', 'q',   DUIH_ABORT);
	inst_set_uih('Q', 'Q',   DUIH_ABORT);
	inst_set_uih(0x03, 0x03, DUIH_ABORT);		/* ^c */
	inst_set_uih(0x1b, 0x1b, DUIH_ABORT);		/* Esc */

	/* Setup user termination character */
	inst_set_uih(tc, tc, DUIH_TERM);

	p->it->capabilities(p->it, NULL, &cap2, NULL);

	/* See if we should calibrate the display update */
	if (!p->update_delay_set && (cap2 & inst2_meas_disp_update) != 0) {
		int cdelay, mdelay;

		/* Set white with a normal delay */
		if ((rv = p->dw->set_color(p->dw, 1.0, 1.0, 1.0)) != 0) {
			a1logd(p->log,1,"set_color() returned %s\n",rv);
			return 3;
		}
		
		/* Set a zero delay */
		cdelay = p->dw->set_update_delay(p->dw, 0);

		/* Set black with zero delay */
		if ((rv = p->dw->set_color(p->dw, 0.0, 0.0, 0.0)) != 0) {
			a1logd(p->log,1,"set_color() returned %s\n",rv);
			return 3;
		}
		
		/* Measure the delay */
		if ((rv = p->it->meas_delay(p->it, &mdelay)) != inst_ok) {
			a1logd(p->log,1,"warning, measure display update delay failed with '%s' (%s)\n",
				      p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
			p->dw->set_update_delay(p->dw, cdelay);		/* Restore the default */
		} else {
			a1logv(p->log, 1, "Measured display update delay of %d msec",mdelay);
			mdelay += mdelay/2 + 50;
			p->dw->set_update_delay(p->dw, mdelay);
			a1logv(p->log, 1, ", using delay of %d msec\n",mdelay);
		}
		p->update_delay_set = 1;
	}

	/* See if we should do a frequency calibration or display integration time cal. first */
	if (p->it->needs_calibration(p->it) & inst_calt_ref_freq
	 && npat > 0
	 && (cols[0].r != 1.0 || cols[0].g != 1.0 || cols[0].b != 1.0)) {
		col tc;
		inst_cal_type calt = inst_calt_ref_freq;
		inst_cal_cond calc = inst_calc_emis_white;

		if ((rv = p->dw->set_color(p->dw, 1.0, 1.0, 1.0)) != 0) {
			a1logd(p->log,1,"set_color() returned %s\n",rv);
			return 3;
		}
		/* Do calibrate, but ignore return code. Press on regardless. */
		if ((rv = p->it->calibrate(p->it, &calt, &calc, id)) != inst_ok) {
			a1logd(p->log,1,"warning, frequency calibrate failed with '%s' (%s)\n",
				      p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
		}
	}

	/* See if we should do a display integration calibration first */
	if (p->it->needs_calibration(p->it) & inst_calt_emis_int_time) {
		col tc;
		inst_cal_type calt = inst_calt_emis_int_time;
		inst_cal_cond calc = inst_calc_emis_white;

		if ((rv = p->dw->set_color(p->dw, 1.0, 1.0, 1.0)) != 0) {
			a1logd(p->log,1,"set_color() returned %s\n",rv);
			return 3;
		}
		/* Do calibrate, but ignore return code. Press on regardless. */
		if ((rv = p->it->calibrate(p->it, &calt, &calc, id)) != inst_ok) {
			a1logd(p->log,1,"warning, display integration calibrate failed with '%s' (%s)\n",
				      p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
		}
	}

	for (patch = 0; patch < npat; patch++) {
		col *scb = &cols[patch];
		double rgb[3];

		scb->mtype = inst_mrt_none;
		scb->XYZ_v = 0;		/* No readings are valid */
		scb->sp.spec_n = 0;
		scb->duration = 0.0;

		if (spat != 0 && tpat != 0)
			a1logv(p->log, 1, "%cpatch %d of %d",cr_char,spat + (noinc != 0 ? 0 : patch),tpat);
		a1logd(p->log,1,"About to read patch %d\n",patch);

		rgb[0] = scb->r;
		rgb[1] = scb->g;
		rgb[2] = scb->b;

		/* If we are doing a soft cal, apply it to the test color */
		if (p->softcal && p->cal[0][0] >= 0.0) {
			int j;
			double inputEnt_1 = (double)(p->ncal-1);

			for (j = 0; j < 3; j++) {
				unsigned int ix;
				double val, w;
				val = rgb[j] * inputEnt_1;
				if (val < 0.0) {
					val = 0.0;
				} else if (val > inputEnt_1) {
					val = inputEnt_1;
				}
				ix = (unsigned int)floor(val);		/* Coordinate */
				if (ix > (p->ncal-2))
					ix = (p->ncal-2);
				w = val - (double)ix;		/* weight */
				val = p->cal[j][ix];
				rgb[j] = val + w * (p->cal[j][ix+1] - val);
			}
		}
		if ((rv = p->dw->set_color(p->dw, rgb[0], rgb[1], rgb[2])) != 0) {
			a1logd(p->log,1,"set_color() returned %s\n",rv);
			return 3;
		}

		/* Until we give up retrying */
		for (;;) {
			val.mtype = inst_mrt_none;
			val.XYZ_v = 0;		/* No readings are valid */
			val.sp.spec_n = 0;
			val.duration = 0.0;
	
			if ((rv = p->it->read_sample(p->it, scb->id, &val, 1)) != inst_ok
			     && (rv & inst_mask) != inst_user_trig) {
				a1logd(p->log,1,"read_sample returned '%s' (%s)\n",
			       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));

				/* deal with a user terminate or abort */
				if ((rv & inst_mask) == inst_user_abort) {
					int keyc = inst_get_uih_char();

					/* Deal with a user terminate */
					if (keyc & DUIH_TERM) {
						return 4;

					/* Deal with a user abort */
					} else if (keyc & DUIH_ABORT) {
						empty_con_chars();
						printf("\nSample read stopped at user request!\n");
						printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
						if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
							printf("\n");
							return 1;
						}
						printf("\n");
						continue;
					}

				/* Deal with needing calibration */
				} else if ((rv & inst_mask) == inst_needs_cal) {
					disp_win_info dwi;
					dwi.dw = p->dw;		/* Set window to use */
					printf("\nSample read failed because instruments needs calibration\n");
					rv = inst_handle_calibrate(p->it, inst_calt_needed, inst_calc_none,
					                                        setup_display_calibrate, &dwi);
					setup_display_calibrate(p->it, inst_calc_none, &dwi); 
					if (rv != inst_ok) {	/* Abort or fatal error */
						return 1;
					}
					continue;

				/* Deal with a bad sensor position */
				} else if ((rv & inst_mask) == inst_wrong_config) {
					empty_con_chars();
					printf("\n\nSpot read failed due to the sensor being in the wrong position\n");
					printf("(%s)\n",p->it->interp_error(p->it, rv));
					printf("Correct position then hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
					if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
						printf("\n");
						return 1;
					}
					printf("\n");
					continue;

				/* Deal with a misread */
				} else if ((rv & inst_mask) == inst_misread) {
					empty_con_chars();
					printf("\nSample read failed due to misread\n");
					printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
					if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
						printf("\n");
						return 1;
					}
					printf("\n");
					continue;

				/* Deal with a communications error */
				} else if ((rv & inst_mask) == inst_coms_fail) {
					empty_con_chars();
					printf("\nSample read failed due to communication problem.\n");
					printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
					if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
						printf("\n");
						return 1;
					}
					printf("\n");
					if (p->it->icom->port_type(p->it->icom) == icomt_serial) {
						/* Allow retrying at a lower baud rate */
						int tt = p->it->last_scomerr(p->it);
						if (tt & (ICOM_BRK | ICOM_FER | ICOM_PER | ICOM_OER)) {
							if (p->br == baud_19200) p->br = baud_9600;
							else if (p->br == baud_9600) p->br = baud_4800;
							else if (p->br == baud_2400) p->br = baud_1200;
							else p->br = baud_1200;
						}
						if ((rv = p->it->init_coms(p->it, p->br, p->fc, 15.0)) != inst_ok) {
							a1logd(p->log,1,"init_coms returned '%s' (%s)\n",
						       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
							return 2;
						}
					}
				continue;
				}
			} else {
				break;		/* Sucesful reading */
			}
		}
		/* We only fall through with a valid reading */
		scb->serno = p->serno++;
		scb->msec = msec_time();

		a1logd(p->log,1, "got reading %f %f %f, transfering to col\n",
		                val.XYZ[0], val.XYZ[1], val.XYZ[2]);

		scb->mtype = val.mtype;

		/* Copy XYZ */
		if (val.XYZ_v != 0) {
			for (j = 0; j < 3; j++)
				scb->XYZ[j] = val.XYZ[j];
			scb->XYZ_v = 1;
		}

		/* Copy spectral */
		if (p->spectral && val.sp.spec_n > 0) {
			scb->sp = val.sp;
		}
		a1logd(p->log,1,"on to next reading\n");
	}
	/* Final return. */
	if (acr && spat != 0 && tpat != 0 && (spat+patch-1) == tpat)
		a1logv(p->log, 1, "\n");
	return 0;
}

/* A structure to hold info about drift samples */
typedef struct {
	col dcols[2];	/* Black and white readings */

	int off, count;	/* Offset and count into cols */

} dsamples;

/* Take a series of readings from the display - drift compensation */
/* Return nz on fail/abort */
/* 1 = user aborted */
/* 2 = instrument access failed */
/* 3 = window access failed */ 
/* 4 = user hit terminate key */
/* 5 = system error */
/* Use disprd_err() to interpret it */
static int disprd_read_drift(
	disprd *p,
	col *cols,		/* Array of patch colors to be tested */
	int npat, 		/* Number of patches to be tested */
	int spat,		/* Start patch index for "verb", 0 if not used */
	int tpat,		/* Total patch index for "verb", 0 if not used */
	int acr,		/* If nz, do automatic final carriage return */
	int tc,			/* If nz, termination key */
	instClamping clamp	/* NZ if clamp XYZ/Lab to be +ve */
) {
	int rv, i, j, e;
	double fper, foff;
	int off, poff;
	int boff, eoff, dno;	/* b&w offsets and count */

//printf("~1 DRIFT_EPERIOD %d, DRIFT_IPERIOD %d, npat %d\n",DRIFT_EPERIOD,DRIFT_IPERIOD,npat);

	/* Figure number and offset for b&w */
	if (p->bdrift == 0) {	/* Must be just wdrift */
		boff = eoff = 1;
		dno = 1;
	} else if (p->bdrift == 0) {	/* Must be just bdrift */
		boff = eoff = 0;
		dno = 1;
	} else {				/* Must be both */
		boff = eoff = 0;
		dno = 2;
	}

	/* If the last drift readings are invalid or too old, */
	/* or if we will use interpolation, read b&w */
#ifdef NEVER
	printf("last_bw_v = %d (%d)\n",p->last_bw_v,p->last_bw_v == 0);
	printf("npat = %d > %d, serno %d, last serno %d (%d)\n",npat, DRIFT_EPERIOD, p->serno,p->last_bw[eoff].serno,(npat > DRIFT_EPERIOD && p->serno > p->last_bw[eoff].serno));
	printf("serno - lastserno %d > %d (%d)\n",p->serno - p->last_bw[eoff].serno, DRIFT_EPERIOD,(p->serno - p->last_bw[eoff].serno) > DRIFT_EPERIOD);
	printf("msec %d - last %d = %d > 10000 (%d)\n",msec_time(),p->last_bw[boff].msec,msec_time() - p->last_bw[boff].msec,(msec_time() - p->last_bw[eoff].msec) > 10000);
#endif /* NEVER */
	if (p->last_bw_v == 0											/* There are none */
	 || (npat > DRIFT_EPERIOD && p->serno > p->last_bw[eoff].serno)	/* We will interpolate */
	 || (p->serno - p->last_bw[eoff].serno - dno) > DRIFT_EPERIOD	/* extrapolate would be too far */
	 || (msec_time() - p->last_bw[eoff].msec) > 10000) {	/* The current is too long ago */

//printf("~1 refreshing last bw\n");
		/* Read the black and/or white drift patch */
		p->last_bw[0].r = 
		p->last_bw[0].g = 
		p->last_bw[0].b = 0.0; 
		p->last_bw[1].r = 
		p->last_bw[1].g = 
		p->last_bw[1].b = 1.0; 
		if ((rv = disprd_read_imp(p, &p->last_bw[boff], dno, spat, tpat, 0, 1, tc, 0)) != 0) {
			return rv;
		}
		p->last_bw_v = 1;

		/* If there is no reference b&w, set them from this first b&w */
		if (p->ref_bw_v == 0) {
			p->ref_bw[0] = p->last_bw[0];
			p->ref_bw[1] = p->last_bw[1];
			p->ref_bw_v = 1;
		}
	}

	/* If there are enough patches to bracket with drift readings */ 
	if (npat > DRIFT_EPERIOD) {
		int ndrift = 2;			/* Number of drift records */
		dsamples *dss;

		/* Figure out the number of drift samples we need */
		ndrift += (npat-1)/DRIFT_IPERIOD;
//printf("~1 spat %d, npat = %d, tpat %d, ndrift = %d\n",spat,npat,tpat,ndrift);

		if ((dss = (dsamples *)calloc(sizeof(dsamples), ndrift)) == NULL) {
			a1logd(p->log,1, "malloc of %d dsamples failed\n",ndrift);
			return 5;
		}
	
		/* Set up bookeeping */
		fper = (double)npat/(ndrift-1.0);
//printf("~1 fper = %f\n",fper);
		foff = 0.0;
		for (poff = off = i = 0; i < ndrift; i++) {
			dss[i].off = off;
			foff += fper;
			off = (int)floor(foff + 0.5);
			if (i < (ndrift-1))
				dss[i].count = off - poff;
			else
				dss[i].count = 0;
			poff = off;
//printf("~1 dss[%d] off = %d, count = %d\n",i, dss[i].off,dss[i].count);

			dss[i].dcols[0].r = 
			dss[i].dcols[0].g = 
			dss[i].dcols[0].b = 0.0; 
			dss[i].dcols[1].r = 
			dss[i].dcols[1].g = 
			dss[i].dcols[1].b = 1.0; 
		}

		/* For each batch of patches bracketed by drift patch readings */
		for (i = 0; i < (ndrift-1); i++) {

			if (i == 0) {	/* Already done very first drift patches, so use them */
				/* Use last b&w */
				dss[i].dcols[0] = p->last_bw[0];
				dss[i].dcols[1] = p->last_bw[1];
			} else {
				/* Read the black and/or white drift patchs before next batch */
				if ((rv = disprd_read_imp(p, &dss[i].dcols[boff], dno, spat+dss[i].off, tpat, 0, 1, tc, 0)) != 0) {
					free(dss);
					return rv;
				}
			}
			/* Read this batch of patches */
			if ((rv = disprd_read_imp(p, &cols[dss[i].off], dss[i].count,spat+dss[i].off,tpat,0,0,tc, 0)) != 0) {
				free(dss);
				return rv;
			}
		}
		/* Read the black and/or white drift patchs after last batch */
		if ((rv = disprd_read_imp(p, &dss[i].dcols[boff], dno, spat+dss[i].off-1, tpat, 0, 1, tc, 0)) != 0) {
			free(dss);
			return rv;
		}
		/* Remember the last for next time */
		p->last_bw[0] = dss[i].dcols[0];
		p->last_bw[1] = dss[i].dcols[1];
		p->last_bw_v = 1;

		/* Set the white drift target to be the last one for batch */
		p->targ_w = p->last_bw[1];
		p->targ_w_v = 1;

//printf("~1 ref b = %f %f %f\n", p->ref_bw[0].XYZ[0], p->ref_bw[0].XYZ[1], p->ref_bw[0].XYZ[2]);
//printf("~1 ref w = %f %f %f\n", p->ref_bw[1].XYZ[0], p->ref_bw[1].XYZ[1], p->ref_bw[1].XYZ[2]);
//printf("~1 ndrift-1 b = %f %f %f\n", dss[ndrift-1].dcols[0].XYZ[0], dss[ndrift-1].dcols[0].XYZ[1], dss[ndrift-1].dcols[0].XYZ[2]);
//printf("~1 ndrift-1 w = %f %f %f\n", dss[ndrift-1].dcols[1].XYZ[0], dss[ndrift-1].dcols[1].XYZ[1], dss[ndrift-1].dcols[1].XYZ[2]);

		/* Apply the drift compensation using interpolation */
		for (i = 0; i < (ndrift-1); i++) {

			for (j = 0; j < dss[i].count; j++) {
				int k = dss[i].off + j;
				double we;		/* Interpolation weight of eairlier value */
				col bb, ww;		/* Interpolated black and white */
//double uXYZ[3];
//icmCpy3(uXYZ, cols[k].XYZ);
//printf("~1 patch %d = %f %f %f\n", k, cols[k].XYZ[0], cols[k].XYZ[1], cols[k].XYZ[2]);
				if (p->bdrift) {
					we = (double)(dss[i+1].dcols[0].msec - cols[k].msec)/
					     (double)(dss[i+1].dcols[0].msec - dss[i].dcols[0].msec);

					if (cols[k].XYZ_v) {
						for (e = 0; e < 3; e++) {
							bb.XYZ[e] =        we  * dss[i].dcols[0].XYZ[e]
							          + (1.0 - we) * dss[i+1].dcols[0].XYZ[e];
						}
//printf("~1 bb = %f %f %f\n", bb.XYZ[0], bb.XYZ[1], bb.XYZ[2]);
					}
					if (cols[k].sp.spec_n > 0) {
						for (e = 0; e < cols[k].sp.spec_n; e++) {
							bb.sp.spec[e] =        we  * dss[i].dcols[0].sp.spec[e]
							          + (1.0 - we) * dss[i+1].dcols[0].sp.spec[e];
						}
					}
				}
				if (p->wdrift) {
					we = (double)(dss[i+1].dcols[1].msec - cols[k].msec)/
					     (double)(dss[i+1].dcols[1].msec - dss[i].dcols[1].msec);

					if (cols[k].XYZ_v) {
						for (e = 0; e < 3; e++) {
							ww.XYZ[e] =        we  * dss[i].dcols[1].XYZ[e]
							          + (1.0 - we) * dss[i+1].dcols[1].XYZ[e];
						}
//printf("~1 ww = %f %f %f\n", ww.XYZ[0], ww.XYZ[1], ww.XYZ[2]);
					}
					if (cols[k].sp.spec_n > 0) {
						for (e = 0; e < cols[k].sp.spec_n; e++) {
							ww.sp.spec[e] =        we  * dss[i].dcols[1].sp.spec[e]
							          + (1.0 - we) * dss[i+1].dcols[1].sp.spec[e];
						}
					}
				}

				if (p->bdrift) {
					/* Compensate interpolated black for any white change from ref. */
					if (p->wdrift) {
						if (cols[k].XYZ_v) {
							for (e = 0; e < 3; e++) {
								bb.XYZ[e] *= p->ref_bw[1].XYZ[e]/ww.XYZ[e];
							}
//printf("~1 wcomp bb = %f %f %f\n", bb.XYZ[0], bb.XYZ[1], bb.XYZ[2]);
						}
						if (cols[k].sp.spec_n > 0) {
							for (e = 0; e < cols[k].sp.spec_n; e++) {
								bb.sp.spec[e] *= p->ref_bw[1].sp.spec[e]/ww.sp.spec[e];
							}
						}
					}

					/* Compensate the reading for any black drift from ref. */
					if (cols[k].XYZ_v) {
						for (e = 0; e < 3; e++) {
							cols[k].XYZ[e] += p->ref_bw[0].XYZ[e] - bb.XYZ[e];
						}
//printf("~1 bcomp patch = %f %f %f\n",  cols[k].XYZ[0], cols[k].XYZ[1], cols[k].XYZ[2]);
					}
					if (cols[k].sp.spec_n > 0) {
						for (e = 0; e < cols[k].sp.spec_n; e++) {
							cols[k].sp.spec[e] += p->ref_bw[0].sp.spec[e] - bb.sp.spec[e];
						}
					}
				}
				/* Compensate the reading for any white drift to target white */
				if (p->wdrift) {
					if (cols[k].XYZ_v) {
						for (e = 0; e < 3; e++) {
							cols[k].XYZ[e] *= p->targ_w.XYZ[e]/ww.XYZ[e];
						}
//printf("~1 wcomp patch = %f %f %f\n",  cols[k].XYZ[0], cols[k].XYZ[1], cols[k].XYZ[2]);
					}
					if (cols[k].sp.spec_n > 0) {
						for (e = 0; e < cols[k].sp.spec_n; e++) {
							cols[k].sp.spec[e] *= p->targ_w.sp.spec[e]/ww.sp.spec[e];
						}
					}
				}
//printf("~1 %d: drift change %f DE\n",k,icmXYZLabDE(&icmD50, uXYZ, cols[k].XYZ));
			}
		}
		free(dss);

	/* Else too small a batch, use extrapolation from the last b&w */
	} else {

//printf("~1 doing small number of readings\n");
		/* Read the small number of patches */
		if ((rv = disprd_read_imp(p, cols,npat,spat,tpat,acr,0,tc,0)) != 0)
			return rv;

		if (p->targ_w_v == 0) {
			/* Set the white drift reference to be the last one */
			p->targ_w = p->last_bw[1];
			p->targ_w_v = 1;
//printf("~1 set white drift target\n");
		}

//printf("~1 ref b = %f %f %f\n", p->ref_bw[0].XYZ[0], p->ref_bw[0].XYZ[1], p->ref_bw[0].XYZ[2]);
//printf("~1 ref w = %f %f %f\n", p->ref_bw[1].XYZ[0], p->ref_bw[1].XYZ[1], p->ref_bw[1].XYZ[2]);
//printf("~1 last b = %f %f %f\n", p->last_bw[0].XYZ[0], p->last_bw[0].XYZ[1], p->last_bw[0].XYZ[2]);
//printf("~1 last w = %f %f %f\n", p->last_bw[1].XYZ[0], p->last_bw[1].XYZ[1], p->last_bw[1].XYZ[2]);
//printf("~1 target w = %f %f %f\n", p->targ_w.XYZ[0], p->targ_w.XYZ[1], p->targ_w.XYZ[2]);
		/* Apply the drift compensation using extrapolation */
		for (j = 0; j < npat; j++) {
			double we;		/* Interpolation weight of eairlier value */
			col bb, ww;		/* Interpolated black and white */

//printf("~1 patch %d = %f %f %f\n", j, cols[j].XYZ[0], cols[j].XYZ[1], cols[j].XYZ[2]);
//double uXYZ[3];
//icmCpy3(uXYZ, cols[j].XYZ);

			if (p->bdrift) {
				bb = p->last_bw[0];
//printf("~1 bb = %f %f %f\n", bb.XYZ[0], bb.XYZ[1], bb.XYZ[2]);
			}
			if (p->wdrift) {
				ww = p->last_bw[1];
//printf("~1 ww = %f %f %f\n", ww.XYZ[0], ww.XYZ[1], ww.XYZ[2]);
			}

			if (p->bdrift) {
				/* Compensate interpolated black for any white change from ref. */
				if (p->wdrift) {
					if (cols[j].XYZ_v) {
						for (e = 0; e < 3; e++) {
							bb.XYZ[e] *= p->ref_bw[1].XYZ[e]/ww.XYZ[e];
						}
//printf("~1 wcomp bb = %f %f %f\n", bb.XYZ[0], bb.XYZ[1], bb.XYZ[2]);
					}
					if (cols[j].sp.spec_n > 0) {
						for (e = 0; e < cols[j].sp.spec_n; e++) {
							bb.sp.spec[e] *= p->ref_bw[1].sp.spec[e]/ww.sp.spec[e];
						}
					}
				}

				/* Compensate the reading for any black drift from ref. */
				if (cols[j].XYZ_v) {
					for (e = 0; e < 3; e++) {
						cols[j].XYZ[e] += p->ref_bw[0].XYZ[e] - bb.XYZ[e];
					}
//printf("~1 bcomp patch = %f %f %f\n",  cols[j].XYZ[0], cols[j].XYZ[1], cols[j].XYZ[2]);
				}
				if (cols[j].sp.spec_n > 0) {
					for (e = 0; e < cols[j].sp.spec_n; e++) {
						cols[j].sp.spec[e] += p->ref_bw[0].sp.spec[e] - bb.sp.spec[e];
					}
				}
			}
			/* Compensate the reading for any white drift to target white */
			if (p->wdrift) {
				if (cols[j].XYZ_v) {
					for (e = 0; e < 3; e++) {
						cols[j].XYZ[e] *= p->targ_w.XYZ[e]/ww.XYZ[e];
					}
//printf("~1 wcomp patch = %f %f %f\n",  cols[j].XYZ[0], cols[j].XYZ[1], cols[j].XYZ[2]);
				}
				if (cols[j].sp.spec_n > 0) {
					for (e = 0; e < cols[j].sp.spec_n; e++) {
						cols[j].sp.spec[e] *= p->targ_w.sp.spec[e]/ww.sp.spec[e];
					}
				}
			}
//printf("~1 %d: drift change %f DE\n",j,icmXYZLabDE(&icmD50, uXYZ, cols[j].XYZ));
		}
	}

	if (acr && spat != 0 && tpat != 0 && (spat+npat-1) == tpat)
		a1logv(p->log, 1, "\n");

	if (clamp) {
		for (j = 0; j < npat; j++) {
			if (cols[j].XYZ_v)
				icmClamp3(cols[j].XYZ, cols[j].XYZ);
		}
	}
	return rv;
}

/* Reset the white drift target white value, for non-batch */
/* readings when white drift comp. is enabled */
static void disprd_reset_targ_w(disprd *p) {
	p->targ_w_v = 0;
}

/* Change the black/white drift compensation options. */
/* Note that this simply invalidates any reference readings, */
/* and therefore will not make for good black compensation */
/* if it is done a long time since the instrument calibration. */
static void disprd_change_drift_comp(disprd *p,
	int bdrift,			/* Flag, nz for black drift compensation */
	int wdrift			/* Flag, nz for white drift compensation */
) {
	if (p->bdrift && !bdrift) {		/* Turning black drift off */
		p->bdrift = 0;
		p->ref_bw_v = 0;
		p->last_bw_v = 0;
		p->targ_w_v = 0;

	} else if (!p->bdrift && bdrift) {	/* Turning black drift on */
		p->bdrift = 1;
		p->ref_bw_v = 0;
		p->last_bw_v = 0;
		p->targ_w_v = 0;
	}

	if (p->wdrift && !wdrift) {		/* Turning white drift off */
		p->wdrift = 0;
		p->ref_bw_v = 0;
		p->last_bw_v = 0;
		p->targ_w_v = 0;

	} else if (!p->wdrift && wdrift) {	/* Turning white drift on */
		p->wdrift = 1;
		p->ref_bw_v = 0;
		p->last_bw_v = 0;
		p->targ_w_v = 0;
	}
}

/* Take a series of readings from the display */
/* Return nz on fail/abort */
/* 1 = user aborted */
/* 2 = instrument access failed */
/* 3 = window access failed */ 
/* 4 = user hit terminate key */
/* 5 = system error */
/* Use disprd_err() to interpret it */
static int disprd_read(
	disprd *p,
	col *cols,		/* Array of patch colors to be tested */
	int npat, 		/* Number of patches to be tested */
	int spat,		/* Start patch index for "verb", 0 if not used */
	int tpat,		/* Total patch index for "verb", 0 if not used */
	int acr,		/* If nz, do automatic final carriage return */
	int tc,			/* If nz, termination key */
	instClamping clamp	/* NZ if clamp XYZ/Lab to be +ve */
) {
	int rv, i;

	if (p->bdrift || p->wdrift) {
		if ((rv = disprd_read_drift(p, cols,npat,spat,tpat,acr,tc,clamp)) != 0)
			return rv;
	} else {
		if ((rv = disprd_read_imp(p, cols,npat,spat,tpat,acr,0,tc,clamp)) != 0)
			return rv;
	}

	/* Use spectral if available. */
	/* Since this is a display, assume that this is absolute spectral */
	if (p->sp2cie != NULL) {
		for (i = 0; i < npat; i++) {
			if (cols[i].sp.spec_n > 0) {
				p->sp2cie->convert(p->sp2cie, cols[i].XYZ, &cols[i].sp);
				if (clamp)
					icmClamp3(cols[i].XYZ, cols[i].XYZ);
				cols[i].XYZ_v = 1;
			}
		}
	}

	return rv;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Return the refresh mode and cbid */
static void disprd_get_disptype(disprd *p, int *refrmode, int *cbid) {
	if (refrmode != NULL)
		*refrmode = p->refrmode;
	if (cbid != NULL)
		*cbid = p->cbid;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static int config_inst_displ(disprd *p);

/* Take an ambient reading if the instrument has the capability. */
/* return nz on fail/abort */
/* 1 = user aborted */
/* 2 = instrument access failed */
/* 4 = user hit terminate key */
/* 5 = system error */
/* 8 = no ambient capability */ 
/* Use disprd_err() to interpret it */
int disprd_ambient(
	struct _disprd *p,
	double *ambient,		/* return ambient in cd/m^2 */
	int tc					/* If nz, termination key */
) {
	inst_mode cap = 0;
	inst2_capability cap2 = 0;
	inst3_capability cap3 = 0;
	inst_opt_type trigmode = inst_opt_unknown;  /* Chosen trigger mode */
	int uswitch = 0;                /* Instrument switch is enabled */
	ipatch val;
	int rv;

	if (p->it != NULL) { /* Not fake */
		p->it->capabilities(p->it, &cap, &cap2, &cap3);
	}
	
	if (!IMODETST(cap, inst_mode_emis_ambient)) {
		printf("Need ambient measurement capability,\n");
		printf("but instrument doesn't support it\n");
		return 8;
	}

	printf("\nPlease make sure the instrument is fitted with\n");
	printf("the appropriate ambient light measuring head.\n");
	
	if ((rv = p->it->set_mode(p->it, inst_mode_emis_ambient)) != inst_ok) {
		a1logd(p->log,1,"set_mode returned '%s' (%s)\n",
		       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
		return 2;
	}
	if (p->it != NULL) { /* Not fake */
		p->it->capabilities(p->it, &cap, &cap2, &cap3);
	}
	
	/* Select a reasonable trigger mode */
	if (cap2 & inst2_user_switch_trig) {
		trigmode = inst_opt_trig_user_switch;
		uswitch = 1;

	/* Or go for user via uicallback trigger */
	} else if (cap2 & inst2_user_trig) {
		trigmode = inst_opt_trig_user;

	/* Or something is wrong with instrument capabilities */
	} else {
		printf("No reasonable trigger mode avilable for this instrument\n");
		return 2;
	}

	if ((rv = p->it->get_set_opt(p->it, trigmode)) != inst_ok) {
		printf("\nSetting trigger mode failed with error :'%s' (%s)\n",
       	       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
		return 2;
	}

	/* Setup the keyboard trigger to return our commands */
	inst_set_uih(0x0, 0xff,  DUIH_TRIG);
	inst_set_uih('q', 'q',   DUIH_ABORT);
	inst_set_uih('Q', 'Q',   DUIH_ABORT);
	inst_set_uih(0x03, 0x03, DUIH_ABORT);		/* ^c */
	inst_set_uih(0x1b, 0x1b, DUIH_ABORT);		/* Esc */

	/* Setup user termination character */
	inst_set_uih(tc, tc, DUIH_TERM);

	/* Until we give up retrying */
	for (;;) {
		char ch;
		val.mtype = inst_mrt_none;
		val.XYZ_v = 0;		/* No readings are valid */
		val.sp.spec_n = 0;
		val.duration = 0.0;

		printf("\nPlace the instrument so as to measure ambient upwards, beside the display,\n");
		if (uswitch)
			printf("Hit ESC or Q to exit, instrument switch or any other key to take a reading: ");
		else
			printf("Hit ESC or Q to exit, any other key to take a reading: ");
		fflush(stdout);

		if ((rv = p->it->read_sample(p->it, "AMBIENT", &val, 1)) != inst_ok
		     && (rv & inst_mask) != inst_user_trig) {
			a1logd(p->log,1,"read_sample returned '%s' (%s)\n",
		       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));

			/* deal with a user terminate or abort */
			if ((rv & inst_mask) == inst_user_abort) {
				int keyc = inst_get_uih_char();

				/* Deal with a user terminate */
				if (keyc & DUIH_TERM) {
					return 4;

				/* Deal with a user abort */
				} else if (keyc & DUIH_ABORT) {
					empty_con_chars();
					printf("\nMeasure stopped at user request!\n");
					printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
					if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
						printf("\n");
						return 1;
					}
					printf("\n");
					continue;
				}

			/* Deal with needs calibration */
			} else if ((rv & inst_mask) == inst_needs_cal) {
				disp_win_info dwi;
				dwi.dw = p->dw;		/* Set window to use */
				printf("\nSample read failed because instruments needs calibration\n");
				rv = inst_handle_calibrate(p->it, inst_calt_needed, inst_calc_none,
				                                        setup_display_calibrate, &dwi);
				setup_display_calibrate(p->it,inst_calc_none, &dwi); 
				if (rv != inst_ok) {	/* Abort or fatal error */
					return 1;
				}
				continue;

			/* Deal with a bad sensor position */
			} else if ((rv & inst_mask) == inst_wrong_config) {
				empty_con_chars();
				printf("\n\nSpot read failed due to the sensor being in the wrong position\n");
				printf("(%s)\n",p->it->interp_error(p->it, rv));
				printf("Correct position then hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
				if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
					printf("\n");
					return 1;
				}
				printf("\n");
				continue;

			/* Deal with a misread */
			} else if ((rv & inst_mask) == inst_misread) {
				empty_con_chars();
				printf("\nMeasurement failed due to misread\n");
				printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
				if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
					printf("\n");
					return 1;
				}
				printf("\n");
				continue;

			/* Deal with a communications error */
			} else if ((rv & inst_mask) == inst_coms_fail) {
				empty_con_chars();
				printf("\nMeasurement read failed due to communication problem.\n");
				printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
				if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
					printf("\n");
					return 1;
				}
				printf("\n");
				if (p->it->icom->port_type(p->it->icom) == icomt_serial) {
					/* Allow retrying at a lower baud rate */
					int tt = p->it->last_scomerr(p->it);
					if (tt & (ICOM_BRK | ICOM_FER | ICOM_PER | ICOM_OER)) {
						if (p->br == baud_19200) p->br = baud_9600;
						else if (p->br == baud_9600) p->br = baud_4800;
						else if (p->br == baud_2400) p->br = baud_1200;
						else p->br = baud_1200;
					}
					if ((rv = p->it->init_coms(p->it, p->br, p->fc, 15.0)) != inst_ok) {
						a1logd(p->log,1,"init_coms returned '%s' (%s)\n",
					       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
						return 2;
					}
				}
			continue;
			}
		} else {
			break;		/* Sucesful reading */
		}
	}
	/* Convert spectral to XYZ */
	if (p->sp2cie != NULL && val.sp.spec_n > 0) {
		p->sp2cie->convert(p->sp2cie, val.XYZ, &val.sp);
		icmClamp3(val.XYZ, val.XYZ);
		val.XYZ_v = 1;
	}

	if (val.XYZ_v == 0) {
		printf("Unexpected failure to get measurement\n");
		return 2;
	}

	a1logd(p->log,1,"Measured ambient of %f\n",val.XYZ[1]);
	if (ambient != NULL)
		*ambient = val.XYZ[1];
	
	/* Configure the instrument mode back to reading the display */
	if ((rv = config_inst_displ(p)) != 0)
		return rv;

	printf("\nPlace the instrument back on the test window\n");
	fflush(stdout);

	return 0;
}


/* Test without a spectrometer using a fake device */
/* Return nz on fail/abort */
/* 1 = user aborted */
/* 2 = instrument access failed */
/* 3 = window access failed */ 
/* 4 = user hit terminate key */
/* 5 = system error */
/* Use disprd_err() to interpret it */
static int disprd_fake_read(
	disprd *p,
	col *cols,		/* Array of patch colors to be tested */
	int npat, 		/* Number of patches to be tested */
	int spat,		/* Start patch index for "verb", 0 if not used */
	int tpat,		/* Total patch index for "verb", 0 if not used */
	int acr,		/* If nz, do automatic final carriage return */
	int tc,			/* If nz, termination key */
	instClamping clamp	/* NZ if clamp XYZ/Lab to be +ve */
) {
	icmXYZNumber white;		/* White point */
	icmXYZNumber red;		/* Red colorant */
	icmXYZNumber green;		/* Green colorant */
	icmXYZNumber blue;		/* Blue colorant */
	double doff[3];			/* device offsets */
	double mat[3][3];		/* Destination matrix */
	double xmat[3][3];		/* Extra matrix */
	double ooff[3];			/* XYZ offsets */
	double rgb[3];			/* Given test values */
	double crgb[3];			/* Calibrated test values */
//	double br = 35.4;		/* Overall brightness */
	double br = 120.0;		/* Overall brightness */
	int patch, j;
	int ttpat = tpat;
	inst_code (*uicallback)(void *, inst_ui_purp) = NULL;
	void *uicontext = NULL;

	if (ttpat < npat)
		ttpat = npat;

	/* Setup fake device */
	white.X = br * 0.955;		/* Somewhere between D50 and D65 */
	white.Y = br * 1.00;
	white.Z = br * 0.97;
	red.X = br * 0.41;
	red.Y = br * 0.21;
	red.Z = br * 0.02;
	green.X = br * 0.30;
	green.Y = br * 0.55;
	green.Z = br * 0.15;
	blue.X = br * 0.15;
	blue.Y = br * 0.10;
	blue.Z = br * 0.97;
#ifdef SIMPLE_MODEL
	doff[0] = doff[1] = doff[2] = 0.0; /* Input offset */
	ooff[0] = ooff[1] = ooff[2] = 0.0; /* Output offset */
#else
	/* Input offset, equivalent to RGB offsets having various values */
	doff[0] = 0.10;
	doff[1] = 0.06;
	doff[2] = 0.08;
	/* Output offset - equivalent to flare [range 0.0 - 1.0] */
	ooff[0] = 0.03;
	ooff[1] = 0.04;
	ooff[2] = 0.09;
#endif

	if (icmRGBprim2matrix(white, red, green, blue, mat))
		error("Fake read unexpectedly got singular matrix\n");

	icmSetUnity3x3(xmat);

	if (p->fake2 == 1) {
		/* Target matrix */
		xmat[0][0] = 1.0; xmat[0][1] = 0.01; xmat[0][2] = 0.02;
		xmat[1][0] = 0.1; xmat[1][1] = 1.11; xmat[1][2] = 0.12;
		xmat[2][0] = 0.2; xmat[2][1] = 0.2;  xmat[2][2] = 1.22;
	}

	uicallback = inst_get_uicallback();
	uicontext = inst_get_uicontext();

	/* Setup the keyboard trigger to return our commands */
	inst_set_uih(0x0, 0xff,  DUIH_TRIG);
	inst_set_uih('q', 'q',   DUIH_ABORT);
	inst_set_uih('Q', 'Q',   DUIH_ABORT);
	inst_set_uih(0x03, 0x03, DUIH_ABORT);		/* ^c */
	inst_set_uih(0x1b, 0x1b, DUIH_ABORT);		/* Esc */

	/* Setup user termination character */
	inst_set_uih(tc, tc, DUIH_TERM);

	for (patch = 0; patch < npat; patch++) {
		if (spat != 0 && tpat != 0)
			a1logv(p->log, 1, "%cpatch %d of %d",cr_char,spat+patch,tpat);
		crgb[0] = rgb[0] = cols[patch].r;
		crgb[1] = rgb[1] = cols[patch].g;
		crgb[2] = rgb[2] = cols[patch].b;

		/* deal with a user terminate or abort */
		if (uicallback(uicontext, inst_measuring) == inst_user_abort) {
			int ch, keyc = inst_get_uih_char();

			/* Deal with a user terminate */
			if (keyc & DUIH_TERM) {
				return 4;

			/* Deal with a user abort */
			} else if (keyc & DUIH_ABORT) {
				empty_con_chars();
				printf("\nSample read stopped at user request!\n");
				printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
				if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
					printf("\n");
					return 1;
				}
				printf("\n");
			}
		}

//printf("~1 patch %d RGB %f %f %f\n",patch,rgb[0], rgb[1], rgb[2]);
		/* If we have a calibration, apply it to the color */
		if (p->cal[0][0] >= 0.0) {
			double inputEnt_1 = (double)(p->ncal-1);

			for (j = 0; j < 3; j++) {
				unsigned int ix;
				double val, w;
				val = rgb[j] * inputEnt_1;
				if (val < 0.0) {
					val = 0.0;
				} else if (val > inputEnt_1) {
					val = inputEnt_1;
				}
				ix = (unsigned int)floor(val);		/* Coordinate */
				if (ix > (p->ncal-2))
					ix = (p->ncal-2);
				w = val - (double)ix;		/* weight */
				val = p->cal[j][ix];
				crgb[j] = val + w * (p->cal[j][ix+1] - val);
			}
//printf("~1 patch %d cal RGB %f %f %f\n",patch, crgb[0], crgb[1], crgb[2]);
		}

		/* If we have a test window, display the patch color */
		if (p->dw) {
			inst_code rv;
			if (p->softcal) {	/* Display value with calibration */
				if ((rv = p->dw->set_color(p->dw, crgb[0], crgb[1], crgb[2])) != 0) {
					a1logd(p->log,1,"set_color() returned %s\n",rv);
					return 3;
				}
			} else {			/* Hardware will apply calibration */
				if ((rv = p->dw->set_color(p->dw, rgb[0], rgb[1], rgb[2])) != 0) {
					a1logd(p->log,1,"set_color() returned %s\n",rv);
					return 3;
				}
			}
		}

		/* Apply the fake devices level of quantization */
#ifdef FAKE_BITS
		if (p->fake2 == 0) {
			for (j = 0; j < 3; j++) {
				int vv;
				vv = (int) (crgb[j] * ((1 << FAKE_BITS) - 1.0) + 0.5);
				crgb[j] =  vv/((1 << FAKE_BITS) - 1.0);
			}
		}
#endif

		/* Apply device offset */
		for (j = 0; j < 3; j++) {
			if (crgb[j] < 0.0)
				crgb[j] = 0.0;
			else if (crgb[j] > 1.0)
				crgb[j] = 1.0;
			crgb[j] = doff[j] + (1.0 - doff[j]) * crgb[j];
		}

		/* Apply gamma */
		for (j = 0; j < 3; j++) {
			if (crgb[j] >= 0.0)
				crgb[j] = pow(crgb[j], 2.5);
			else
				crgb[j] = 0.0;
		}

		/* Convert to XYZ */
		icmMulBy3x3(cols[patch].XYZ, mat, crgb);

		/* Apply XYZ offset */
		for (j = 0; j < 3; j++)
			cols[patch].XYZ[j] += ooff[j];

		/* Apply extra matrix */
		icmMulBy3x3(cols[patch].XYZ, xmat, cols[patch].XYZ);

#ifdef FAKE_NOISE
		if (p->fake2 == 0) {
			cols[patch].XYZ[0] += 2.0 * FAKE_NOISE * d_rand(-1.0, 1.0);
			cols[patch].XYZ[1] += FAKE_NOISE * d_rand(-1.0, 1.0);
			cols[patch].XYZ[2] += 4.0 * FAKE_NOISE * d_rand(-1.0, 1.0);
		}
#endif
		if (clamp)
			icmClamp3(cols[patch].XYZ, cols[patch].XYZ);
//printf("~1 patch %d XYZ %f %f %f\n",patch,cols[patch].XYZ[0], cols[patch].XYZ[1], cols[patch].XYZ[2]);
		cols[patch].XYZ_v = 1;
		cols[patch].sp.spec_n = 0;
		cols[patch].mtype = inst_mrt_emission;
	}
	if (acr && spat != 0 && tpat != 0 && (spat+patch-1) == tpat)
		a1logv(p->log, 1, "\n");
	return 0;
}

/* Test without a spectrometer using a fake ICC profile device */
/* Return nz on fail/abort */
/* 1 = user aborted */
/* 2 = instrument access failed */
/* 3 = window access failed */ 
/* 4 = user hit terminate key */
/* 5 = system error */
/* Use disprd_err() to interpret it */
static int disprd_fake_read_lu(
	disprd *p,
	col *cols,		/* Array of patch colors to be tested */
	int npat, 		/* Number of patches to be tested */
	int spat,		/* Start patch index for "verb", 0 if not used */
	int tpat,		/* Total patch index for "verb", 0 if not used */
	int acr,		/* If nz, do automatic final carriage return */
	int tc,			/* If nz, termination key */
	instClamping clamp	/* NZ if clamp XYZ/Lab to be +ve */
) {
	int patch, j;
	int ttpat = tpat;
	double br = 120.4;		/* Overall brightness */
	inst_code (*uicallback)(void *, inst_ui_purp) = NULL;
	void *uicontext = NULL;

	if (ttpat < npat)
		ttpat = npat;

	uicallback = inst_get_uicallback();
	uicontext = inst_get_uicontext();

	/* Setup the keyboard trigger to return our commands */
	inst_set_uih(0x0, 0xff,  DUIH_TRIG);
	inst_set_uih('q', 'q',   DUIH_ABORT);
	inst_set_uih('Q', 'Q',   DUIH_ABORT);
	inst_set_uih(0x03, 0x03, DUIH_ABORT);		/* ^c */
	inst_set_uih(0x1b, 0x1b, DUIH_ABORT);		/* Esc */

	/* Setup user termination character */
	inst_set_uih(tc, tc, DUIH_TERM);

	for (patch = 0; patch < npat; patch++) {
		double rgb[3];;
		if (spat != 0 && tpat != 0)
			a1logv(p->log, 1, "%cpatch %d of %d",cr_char,spat+patch,tpat);
		rgb[0] = cols[patch].r;
		rgb[1] = cols[patch].g;
		rgb[2] = cols[patch].b;

		/* deal with a user terminate or abort */
		if (uicallback(uicontext, inst_measuring) == inst_user_abort) {
			int ch, keyc = inst_get_uih_char();

			/* Deal with a user terminate */
			if (keyc & DUIH_TERM) {
				return 4;

			/* Deal with a user abort */
			} else if (keyc & DUIH_ABORT) {
				empty_con_chars();
				printf("\nSample read stopped at user request!\n");
				printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
				if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
					printf("\n");
					return 1;
				}
				printf("\n");
			}
		}

		/* If we have a test window, display the patch color */
		if (p->dw) {
			inst_code rv;
			if ((rv = p->dw->set_color(p->dw, rgb[0], rgb[1], rgb[2])) != 0) {
				a1logd(p->log,1,"set_color() returned %s\n",rv);
				return 3;
			}
		}

		/* If we have a RAMDAC, apply it to the color */
		if (p->cal[0][0] >= 0.0) {
			double inputEnt_1 = (double)(p->ncal-1);

			for (j = 0; j < 3; j++) {
				unsigned int ix;
				double val, w;

				val = rgb[j] * inputEnt_1;
				if (val < 0.0) {
					val = 0.0;
				} else if (val > inputEnt_1) {
					val = inputEnt_1;
				}
				ix = (unsigned int)floor(val);		/* Coordinate */
				if (ix > (p->ncal-2))
					ix = (p->ncal-2);
				w = val - (double)ix;		/* weight */
				val = p->cal[j][ix];
				rgb[j] = val + w * (p->cal[j][ix+1] - val);
			}
		}

		p->fake_lu->lookup(p->fake_lu, cols[patch].XYZ, rgb); 
		for (j = 0; j < 3; j++) 
			cols[patch].XYZ[j] *= br;
#ifdef FAKE_NOISE
		cols[patch].XYZ[0] += 2.0 * FAKE_NOISE * d_rand(-1.0, 1.0);
		cols[patch].XYZ[1] += FAKE_NOISE * d_rand(-1.0, 1.0);
		cols[patch].XYZ[2] += 4.0 * FAKE_NOISE * d_rand(-1.0, 1.0);
#endif
		if (clamp)
			icmClamp3(cols[patch].XYZ, cols[patch].XYZ);
		cols[patch].XYZ_v = 1;
		cols[patch].sp.spec_n = 0;
		cols[patch].mtype = inst_mrt_emission;
	}
	if (acr && spat != 0 && tpat != 0 && (spat+patch-1) == tpat)
		a1logv(p->log, 1, "\n");
	return 0;
}

/* Use without a direct connect spectrometer using shell callout */
/* Return nz on fail/abort */
/* 1 = user aborted */
/* 2 = instrument access failed */
/* 3 = window access failed */ 
/* 4 = user hit terminate key */
/* 5 = system error */
/* Use disprd_err() to interpret it */
static int disprd_fake_read_co(disprd *p,
	col *cols,		/* Array of patch colors to be tested */
	int npat, 		/* Number of patches to be tested */
	int spat,		/* Start patch index for "verb", 0 if not used */
	int tpat,		/* Total patch index for "verb", 0 if not used */
	int acr,		/* If nz, do automatic final carriage return */
	int tc,			/* If nz, termination key */
	instClamping clamp	/* NZ if clamp XYZ/Lab to be +ve */
) {
	int patch, j;
	int ttpat = tpat;
	inst_code (*uicallback)(void *, inst_ui_purp) = NULL;
	void *uicontext = NULL;

	if (ttpat < npat)
		ttpat = npat;

	uicallback = inst_get_uicallback();
	uicontext = inst_get_uicontext();

	/* Setup the keyboard trigger to return our commands */
	inst_set_uih(0x0, 0xff,  DUIH_TRIG);
	inst_set_uih('q', 'q',   DUIH_ABORT);
	inst_set_uih('Q', 'Q',   DUIH_ABORT);
	inst_set_uih(0x03, 0x03, DUIH_ABORT);		/* ^c */
	inst_set_uih(0x1b, 0x1b, DUIH_ABORT);		/* Esc */

	/* Setup user termination character */
	inst_set_uih(tc, tc, DUIH_TERM);

	for (patch = 0; patch < npat; patch++) {
		double rgb[3];
		int rv;
		char *cmd;
		FILE *fp;

		/* deal with a user terminate or abort */
		if (uicallback(uicontext, inst_measuring) == inst_user_abort) {
			int ch, keyc = inst_get_uih_char();

			/* Deal with a user terminate */
			if (keyc & DUIH_TERM) {
				return 4;

			/* Deal with a user abort */
			} else if (keyc & DUIH_ABORT) {
				empty_con_chars();
				printf("\nSample read stopped at user request!\n");
				printf("Hit Esc or Q to give up, any other key to retry:"); fflush(stdout);
				if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
					printf("\n");
					return 1;
				}
				printf("\n");
			}
		}

		if (spat != 0 && tpat != 0)
			a1logv(p->log, 1, "%cpatch %d of %d",cr_char,spat+patch,tpat);
		rgb[0] = cols[patch].r;
		rgb[1] = cols[patch].g;
		rgb[2] = cols[patch].b;

		/* If we have a test window, display the patch color */
		if (p->dw) {
			inst_code rv;
			if ((rv = p->dw->set_color(p->dw, rgb[0], rgb[1], rgb[2])) != 0) {
				a1logd(p->log,1,"set_color() returned %s\n",rv);
				return 3;
			}
		}

		/* If we have a RAMDAC, apply it to the color */
		if (p->cal[0][0] >= 0.0) {
			double inputEnt_1 = (double)(p->ncal-1);

			for (j = 0; j < 3; j++) {
				unsigned int ix;
				double val, w;

				val = rgb[j] * inputEnt_1;
				if (val < 0.0) {
					val = 0.0;
				} else if (val > inputEnt_1) {
					val = inputEnt_1;
				}
				ix = (unsigned int)floor(val);		/* Coordinate */
				if (ix > (p->ncal-2))
					ix = (p->ncal-2);
				w = val - (double)ix;		/* weight */
				val = p->cal[j][ix];
				rgb[j] = val + w * (p->cal[j][ix+1] - val);
			}
		}

		if ((cmd = malloc(strlen(p->mcallout) + 200)) == NULL)
			error("Malloc of command string failed");

		sprintf(cmd, "%s %d %d %d %f %f %f",p->mcallout,
			        (int)(rgb[0] * 255.0 + 0.5),
		            (int)(rgb[1] * 255.0 + 0.5),
		            (int)(rgb[2] * 255.0 + 0.5), rgb[0], rgb[1], rgb[2]);
		if ((rv = system(cmd)) != 0)
			error("System command '%s' failed with %d",cmd,rv); 

		/* Now read the XYZ result from the mcallout.meas file */
		sprintf(cmd, "%s.meas",p->mcallout);
		if ((fp = fopen(cmd,"r")) == NULL)
			error("Unable to open measurement value file '%s'",cmd);

		if (fscanf(fp, " %lf %lf %lf", &cols[patch].XYZ[0], &cols[patch].XYZ[1],
		                               &cols[patch].XYZ[2]) != 3)
			error("Unable to parse measurement value file '%s'",cmd);
		fclose(fp);
		free(cmd);

		if (clamp)
			icmClamp3(cols[patch].XYZ, cols[patch].XYZ);
		cols[patch].XYZ_v = 1;
		cols[patch].mtype = inst_mrt_emission;

		a1logv(p->log, 2, "Read XYZ %f %f %f from '%s'\n", cols[patch].XYZ[0],
			                     cols[patch].XYZ[1],cols[patch].XYZ[2], cmd);

	}
	if (acr && spat != 0 && tpat != 0 && (spat+patch-1) == tpat)
		a1logv(p->log, 1, "\n");
	return 0;
}

/* Return a string describing the error code */
char *disprd_err(int en) {
	switch(en) {
		case 1:
			return "User Aborted";
		case 2:
			return "Instrument Access Failed";
		case 22:
			return "Instrument Access Failed (No PLD Pattern - have you run spyd2en ?)";
		case 3:
			return "Window Access Failed";
		case 4:
			return "VideoLUT Access Failed";
		case 5:
			return "User Terminated";
		case 6:
			return "System Error";
		case 7:
			return "Either CRT or LCD must be selected";
		case 8:
			return "Instrument has no ambient measurement capability";
		case 9:
			return "Creating spectral conversion object failed";
		case 10:
			return "Instrument has no CCMX capability";
		case 11:
			return "Instrument has no CCSS capability";
		case 12:
			return "Internal: trying to set calibration when not using calibration";
	}
	return "Unknown";
}

static void disprd_del(disprd *p) {

	if (p->log->verb >= 1 && p->bdrift && p->ref_bw_v && p->last_bw_v) {
		icmXYZNumber w;
		double de;
		icmAry2XYZ(w, p->ref_bw[1].XYZ);
		de = icmXYZLabDE(&w, p->ref_bw[0].XYZ, p->last_bw[0].XYZ);
		a1logv(p->log, 1, "Black drift was %f DE\n",de);
	}
	if (p->log->verb >= 1 && p->wdrift && p->ref_bw_v && p->last_bw_v) {
		icmXYZNumber w;
		double de;
		icmAry2XYZ(w, p->ref_bw[1].XYZ);
		de = icmXYZLabDE(&w, p->ref_bw[1].XYZ, p->last_bw[1].XYZ);
		a1logv(p->log, 1, "White drift was %f DE\n",de);
	}

	/* The user may remove the instrument */
	if (p->dw != NULL)
		printf("The instrument can be removed from the screen.\n");

	if (p->fake_lu != NULL)
		p->fake_lu->del(p->fake_lu);
	if (p->fake_icc != NULL)
		p->fake_icc->del(p->fake_icc);
	if (p->fake_fp != NULL)
		p->fake_fp->del(p->fake_fp);

	if (p->it != NULL)
		p->it->del(p->it);
	if (p->dw != NULL) {
		if (p->or != NULL)
			p->dw->set_ramdac(p->dw,p->or, 0);
		p->dw->del(p->dw);
	}
	if (p->sp2cie != NULL)
		p->sp2cie->del(p->sp2cie); 
	p->log = del_a1log(p->log);
	free(p);
}

/* Helper to configure the instrument mode ready */
/* for reading the display */
/* return new_disprd() error code */
static int config_inst_displ(disprd *p) {
	inst_mode cap;
	inst2_capability cap2;
	inst3_capability cap3;
	inst_mode mode = 0;
	int rv;
	
	p->it->capabilities(p->it, &cap, &cap2, &cap3);
	
	if (p->tele && !IMODETST(cap, inst_mode_emis_tele)) {
		printf("Want telephoto measurement capability but instrument doesn't support it\n");
		printf("so falling back to spot mode.\n");
		a1logd(p->log,1,"No telephoto mode so falling back to spot mode.\n");
		p->tele = 0;
	}
	
	if (( p->tele && !IMODETST(cap, inst_mode_emis_tele))
	 || (!p->tele && !IMODETST(cap, inst_mode_emis_spot))) {
		printf("Need %s emissive measurement capability,\n", p->tele ? "telephoto" : "spot");
		printf("but instrument doesn't support it\n");
		a1logd(p->log,1,"Need %s emissive measurement capability but device doesn't support it,\n",
		       p->tele ? "telephoto" : "spot");
		return 2;
	}
	
	if (p->nadaptive && !IMODETST(cap, inst_mode_emis_nonadaptive)) {
		printf("Need non-adaptives measurement mode, but instrument doesn't support it\n");
		a1logd(p->log,1, "Need non-adaptives measurement mode, but instrument doesn't support it\n");
		return 2;
	}
	
	if (p->obType != icxOT_none
	 && p->obType != icxOT_default) {
		if (!IMODETST(cap, inst_mode_spectral) && (cap2 & inst2_ccss) == 0) {
			printf("A non-standard observer was requested,\n");
			printf("but instrument doesn't support spectral or CCSS\n");
			a1logd(p->log,1,"A non-standard observer was requested,\n"
			          "but instrument doesn't support spectral or CCSS\n");
			return 2;
		}
		/* Make sure spectral is turned on if an observer is requested */
		if (!p->spectral && (cap2 & inst2_ccss) == 0)
			p->spectral = 1;
	}

	if (p->spectral && !IMODETST(cap,inst_mode_spectral)) {
		if (p->spectral != 2) {		/* Not soft */
			printf("Spectral information was requested,\n");
			printf("but instrument doesn't support it\n");
			a1logd(p->log,1,"Spectral information was requested,\nbut instrument doesn't support it\n");
			return 2;
		}
		p->spectral = 0;
	}
	
	if (p->tele) {
		mode = inst_mode_emis_tele;
	} else {
		mode = inst_mode_emis_spot;
	}
	if (p->nadaptive)
		mode |= inst_mode_emis_nonadaptive;
	
	if (p->spectral) {
		mode |= inst_mode_spectral;
		p->spectral = 1;
	} else {
		p->spectral = 0;
	}
	
	/* Set the display type */

	if (p->dtype != 0) {
		if (cap2 & inst2_disptype) {
			int ix;
			if ((ix = inst_get_disptype_index(p->it, p->dtype, p->docbid)) < 0) {
				a1logd(p->log,1,"Display type selection '%c' is not valid for instrument\n",p->dtype);
				return 2;
			}
			if ((rv = p->it->set_disptype(p->it, ix)) != inst_ok) {
				a1logd(p->log,1,"Setting display type failed failed with '%s' (%s)\n",
				       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
				return 2;
			}
		} else
			printf("Display type ignored - instrument doesn't support display type\n");
	}

	/* Get the refresh mode and cbid */
	if (cap2 & inst2_disptype) {
		p->it->get_set_opt(p->it, inst_opt_get_dtinfo, &p->refrmode, &p->cbid);
	}
	
	/* Disable initcalibration of machine if selected */
	if (p->noinitcal != 0) {
		if ((rv = p->it->get_set_opt(p->it,inst_opt_noinitcalib, 0)) != inst_ok) {
			a1logd(p->log,1,"Setting no-initial calibrate failed failed with '%s' (%s)\n",
		       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
			printf("Disable initial-calibrate not supported\n");
		}
	}
	
	if (p->highres) {
		if (IMODETST(cap, inst_mode_highres)) {
			inst_code ev;
			if ((ev = p->it->get_set_opt(p->it, inst_opt_highres)) != inst_ok) {
				a1logd(p->log,1,"\nSetting high res mode failed with error :'%s' (%s)\n",
		       	       p->it->inst_interp_error(p->it, ev), p->it->interp_error(p->it, ev));
				return 2;
			}
		} else {
			a1logv(p->log, 1, "high resolution ignored - instrument doesn't support high res. mode\n");
		}
	}
	if ((rv = p->it->set_mode(p->it, mode)) != inst_ok) {
		a1logd(p->log,1,"set_mode returned '%s' (%s)\n",
		       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
		return 2;
	}
	p->it->capabilities(p->it, &cap, &cap2, &cap3);

	if (p->ccmtx != NULL) {
		if ((cap2 & inst2_ccmx) == 0) {
			a1logd(p->log,1,"Instrument doesn't support ccmx correction\n");
			return 10;
		}
		if ((rv = p->it->col_cor_mat(p->it, p->ccmtx)) != inst_ok) {
			a1logd(p->log,1,"col_cor_mat returned '%s' (%s)\n",
			       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
			return 2;
		}
	}
	
	if (p->sets != NULL
	 || ((cap2 & inst2_ccss) != 0 && p->obType != icxOT_default)) {

		if ((cap2 & inst2_ccss) == 0) {
			a1logd(p->log,1,"Instrument doesn't support ccss calibration\n");
			return 11;
		}
		if ((rv = p->it->get_set_opt(p->it, inst_opt_set_ccss_obs, p->obType, p->custObserver))
			                                                                        != inst_ok) {
			a1logd(p->log,1,"inst_opt_set_ccss_obs returned '%s' (%s)\n",
			       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
			return 2;
		}
		if ((rv = p->it->col_cal_spec_set(p->it, p->sets, p->no_sets)) != inst_ok) {
			a1logd(p->log,1,"col_cal_spec_set returned '%s' (%s)\n",
			       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
			return 2;
		}
	}
	
	/* Set the trigger mode to program triggered */
	if ((rv = p->it->get_set_opt(p->it,inst_opt_trig_prog)) != inst_ok) {
		a1logd(p->log,1,"Setting program trigger mode failed failed with '%s' (%s)\n",
	       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
		return 2;
	}

	/* Reset key meanings */
	inst_reset_uih();

	a1logd(p->log,1,"config_inst_displ suceeded\n");
	return 0;
}

/* Create a display reading object. */
/* Return NULL if error */
/* Set *errc to code: */
/* 0 = no error */
/* 1 = user aborted */
/* 2 = instrument access failed */
/* 22 = instrument access failed  - no PLD pattern */
/* 3 = window access failed */ 
/* 4 = RAMDAC access failed */ 
/* 5 = user hit terminate key */
/* 6 = system error */
/* 7 = CRT or LCD must be selected */
/* 9 = spectral conversion failed */
/* 10 = no ccmx support */
/* 11 = no ccss support */
/* 12 = cal to set but native != 0 */
/* Use disprd_err() to interpret *errc */
disprd *new_disprd(
int *errc,          /* Error code. May be NULL (could use log for this instead?) */
icompath *ipath,	/* Instrument path to open, &icomFakeDevice == fake */
flow_control fc,	/* Flow control */
int dtype,			/* Display type selection character */
int docbid,			/* NZ to only allow cbid dtypes */
int tele,			/* NZ for tele mode. Falls back to display mode */
int nadaptive,		/* NZ for non-adaptive mode */
int noinitcal,		/* No initial instrument calibration */
int highres,		/* Use high res mode if available */
int native,			/* 0 = use current current or given calibration curve */
					/* 1 = use native linear out & high precision */
int *noramdac,		/* Return nz if no ramdac access. native is set to 0 */
double cal[3][MAX_CAL_ENT],	/* Calibration set/return (cal[0][0] < 0.0 or NULL if not used) */
					/* native must be 0 if cal is set */
int ncal,			/* Number of cal[] entries */
int softcal,		/* NZ if apply cal to readings rather than hardware */
disppath *disp,		/* Display to calibrate. NULL if fake and no dispwin */
int blackbg,		/* NZ if whole screen should be filled with black */
int override,		/* Override_redirect on X11 */
int webdisp,        /* If nz, port number for web color display */
char *ccallout,		/* Shell callout on set color */
char *mcallout,		/* Shell callout on measure color (forced fake) */
double hpatsize,	/* Size of dispwin */
double vpatsize,
double ho,			/* Horizontal offset */
double vo,			/* Vertical offset */
double ccmtx[3][3],	/* Colorimeter Correction matrix, NULL if none */
xspect *sets,		/* CCSS Set of sample spectra, NULL if none  */
int no_sets,	 	/* CCSS Number on set, 0 if none */
int spectral,		/* Generate spectral info flag */
icxObserverType obType,	/* Use alternate observer if spectral or CCSS and != icxOT_none */
xspect custObserver[3],	/* Optional custom observer */
int bdrift,			/* Flag, nz for black drift compensation */
int wdrift,			/* Flag, nz for white drift compensation */
char *fake_name,	/* Name of profile to use as a fake device */
a1log *log      	/* Verb, debug & error log */
) {
	disprd *p = NULL;
	int ch;
	inst_code rv;

	if (errc != NULL) *errc = 0;		/* default return code = no error */

	if (cal != NULL && cal[0][0] >= 0.0 && native != 0) {
		if (errc != NULL) *errc = 12;
			return NULL;
	}

	/* Allocate a disprd */
	if ((p = (disprd *)calloc(sizeof(disprd), 1)) == NULL) {
		a1logd(log, 1, "new_disprd failed due to malloc failure\n");
		if (errc != NULL) *errc = 6;
		return NULL;
	}
	p->log = new_a1log_d(log);
	p->del = disprd_del;
	p->read = disprd_read;
	p->get_disptype = disprd_get_disptype;
	p->reset_targ_w = disprd_reset_targ_w;
	p->change_drift_comp = disprd_change_drift_comp;
	p->ambient = disprd_ambient;
	p->fake_name = fake_name;

	p->ccmtx = ccmtx;
	p->sets = sets;
	p->no_sets = no_sets;		/* CCSS */
	p->spectral = spectral;
	p->obType = obType;			/* CCSS or spectral */
	p->custObserver = custObserver;
	p->bdrift = bdrift;
	p->wdrift = wdrift;
	p->dtype = dtype;
	p->docbid = docbid;
	p->refrmode = -1;			/* Unknown */
	p->cbid = 0;				/* Unknown */
	p->tele = tele;
	p->nadaptive = nadaptive;
	p->noinitcal = noinitcal;
	p->highres = highres;
	if (mcallout != NULL)
		ipath = &icomFakeDevice;	/* Force fake device */
	p->mcallout = mcallout;
	p->ipath = ipath;
	p->br = baud_19200;
	p->fc = fc;

	/* Save this in case we are using a fake device */
	if (cal != NULL && cal[0][0] >= 0.0) {
		int j, i;
		for (j = 0; j < 3; j++) {
			for (i = 0; i < ncal; i++) {
				p->cal[j][i] = cal[j][i];
			}
		}
		p->ncal = ncal;
		p->softcal = softcal;
	} else {
		p->cal[0][0] =  -1.0;
		p->ncal = 0;
		p->softcal = 0;
	}

	/* If non-real instrument */
	if (ipath == &icomFakeDevice) {
		p->fake = 1;

		p->fake_fp = NULL;
		p->fake_icc = NULL;
		p->fake_lu = NULL;

		/* See if there is a profile we should use as the fake device */
		if (p->mcallout == NULL && p->fake_name != NULL
		 && (p->fake_fp = new_icmFileStd_name(p->fake_name,"r")) != NULL) {
			if ((p->fake_icc = new_icc()) != NULL) {
				if (p->fake_icc->read(p->fake_icc,p->fake_fp,0) == 0) {
					icColorSpaceSignature ins;
					p->fake_lu = p->fake_icc->get_luobj(p->fake_icc, icmFwd, icAbsoluteColorimetric,
					                            icSigXYZData, icmLuOrdNorm);
					p->fake_lu->spaces(p->fake_lu, &ins, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
					if (ins != icSigRgbData) {
						p->fake_lu->del(p->fake_lu);
						p->fake_lu = NULL;
					}
				}
			}
		}

		if (p->fake_lu != NULL) {
			a1logv(p->log, 1, "Using profile '%s' rather than real device\n",p->fake_name);
			p->read = disprd_fake_read_lu;
		} else if (p->mcallout != NULL) {
			a1logv(p->log, 1, "Using shell callout '%s' rather than real device\n",p->mcallout);
			p->read = disprd_fake_read_co;
		} else
			p->read = disprd_fake_read;

		if (disp == NULL) {
			a1logd(log,1,"new_disprd returning fake device\n");
			return p;
		}

	/* Setup the instrument */
	} else {
	
		a1logv(p->log, 1, "Setting up the instrument\n");
	
		if ((p->it = new_inst(ipath, 0, log, DUIH_FUNC_AND_CONTEXT)) == NULL) {
			a1logd(p->log,1,"new_disprd failed because new_inst failed\n");
			p->del(p);
			if (errc != NULL) *errc = 2;
			return NULL;
		}
	
		/* Establish communications */
		if ((rv = p->it->init_coms(p->it, p->br, p->fc, 15.0)) != inst_ok) {
			a1logd(log,1,"init_coms returned '%s' (%s)\n",
			       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
			a1logd(log,1,"new_disprd failed because init_coms failed\n");
			p->del(p);
			if (errc != NULL) *errc = 2;
			return NULL;
		}
	
		/* Initialise the instrument */
		if ((rv = p->it->init_inst(p->it)) != inst_ok) {
			a1logd(log,1,"init_inst returned '%s' (%s)\n",
			       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
			a1logd(log,1,"new_disprd failed because init_inst failed\n");
			p->del(p);
			if (errc != NULL) {
				*errc = 2;
				if ((rv & inst_imask) == SPYD2_NO_PLD_PATTERN)
					*errc = 22;
			}
			return NULL;
		}
	
		/* Configure the instrument mode for reading the display */
		if ((rv = config_inst_displ(p)) != 0) {
			a1logd(log,1,"new_disprd failed because config_inst_displ failed\n");
			p->del(p);
			if (errc != NULL) *errc = rv;
			return NULL;
		}
	}

	/* Create a spectral conversion object if needed */
	if (p->spectral && p->obType != icxOT_none) {
		if ((p->sp2cie = new_xsp2cie(icxIT_none, NULL, p->obType, custObserver, icSigXYZData, icxNoClamp))
		                                                                                 == NULL) {
			a1logd(log,1,"new_disprd failed because creation of spectral conversion object failed\n");
			p->del(p);
			if (errc != NULL) *errc = 9;
			return NULL;
		}
	}

	if (webdisp != 0) {
		/* Open web display */
		if ((p->dw = new_webwin(webdisp, hpatsize, vpatsize, ho, vo, 0, 0,
		                                        p->log->verb, p->log->debug)) == NULL) {
			a1logd(log,1,"new_disprd failed because new_webwin failed\n");
			p->del(p);
			if (errc != NULL) *errc = 3;
			return NULL;
		}
		if (noramdac != NULL)
			*noramdac = 1;
	} else {
		/* Open display window for positioning (no blackbg) */
		if ((p->dw = new_dispwin(disp, hpatsize, vpatsize, ho, vo, 0, native, noramdac, 0,
		                                              override, p->log->debug)) == NULL) {
			a1logd(log,1,"new_disprd failed because new_dispwin failed\n");
			p->del(p);
			if (errc != NULL) *errc = 3;
			return NULL;
		}
	}

	if (p->it != NULL) {
		/* Do a calibration up front, so as not to get in the users way, */
		/* but ignore a CRT frequency or display integration calibration, */
		/* since these will be done automatically. */
		if (p->it->needs_calibration(p->it) & inst_calt_n_dfrble_mask) {
			disp_win_info dwi;
			dwi.dw = p->dw;		/* Set window to use */
	
			rv = inst_handle_calibrate(p->it, inst_calt_needed, inst_calc_none,
			                                  setup_display_calibrate, &dwi);
			setup_display_calibrate(p->it,inst_calc_none, &dwi); 
			printf("\n");
			if (rv != inst_ok) {	/* Abort or fatal error */
				a1logd(log,1,"new_disprd failed because calibrate failed with '%s' (%s)\n",
				       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
				printf("Calibrate failed with '%s' (%s)\n",
				       p->it->inst_interp_error(p->it, rv), p->it->interp_error(p->it, rv));
				p->del(p);
				if (errc != NULL) *errc = 2;
				return NULL;
			}
		}
	}

	/* Ask user to put instrument on screen */
	empty_con_chars();
	printf("Place instrument on test window.\n");
	printf("Hit Esc or Q to give up, any other key to continue:"); fflush(stdout);
	if ((ch = next_con_char()) == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
		printf("\n");
		a1logd(log,1,"new_disprd failed because user aborted when placing device\n");
		p->del(p);
		if (errc != NULL) *errc = 1;
		return NULL;
	}
	printf("\n");

	if (webdisp == 0) {
		/* Close the positioning window */
		if (p->dw != NULL) {
			if (p->or != NULL)
				p->dw->set_ramdac(p->dw,p->or, 0);
			p->dw->del(p->dw);
		}
	
		/* Open display window again for measurement */
		if ((p->dw = new_dispwin(disp, hpatsize, vpatsize, ho, vo, 0, native, noramdac, blackbg,
		                                                    override, p->log->debug)) == NULL) {
			a1logd(log,1,"new_disprd failed new_dispwin failed\n");
			p->del(p);
			if (errc != NULL) *errc = 3;
			return NULL;
		}
	}

	/* Set color change callout */
	if (ccallout) {
		p->dw->set_callout(p->dw, ccallout);
	}

	/* If we have a calibration to set */
	/* (This is only typically the case for disread) */
	if (!p->softcal && cal != NULL && cal[0][0] >= 0.0) {

		/* Save current RAMDAC so that we can restore it */
		p->or = NULL;
		if ((p->or = p->dw->get_ramdac(p->dw)) == NULL) {
			warning("Unable to read or set display RAMDAC - switching to softcal");
			p->softcal = softcal = 1;
		}

		/* Set the given RAMDAC so we can characterise through it */
		if (p->or != NULL) {
			ramdac *r;
			int j, i;
			
			r = p->or->clone(p->or);

			/* Set the ramdac contents. */
			/* We linearly interpolate from cal[ncal] to RAMDAC[nent] resolution */
			for (i = 0; i < r->nent; i++) {
				double val, w;
				unsigned int ix;

				val = (ncal-1.0) * i/(r->nent-1.0);
				ix = (unsigned int)floor(val);		/* Coordinate */
				if (ix > (ncal-2))
					ix = (ncal-2);
				w = val - (double)ix;		/* weight */
				for (j = 0; j < 3; j++) {
					val = cal[j][ix];
					r->v[j][i] = val + w * (cal[j][ix+1] - val);
				}
			}
			if (p->dw->set_ramdac(p->dw, r, 0)) {
				a1logd(log,1,"new_disprd failed becayse set_ramdac failed\n");
				a1logv(p->log, 1, "Failed to set RAMDAC to desired calibration.\n");
				a1logv(p->log, 1, "Perhaps the operating system is being fussy ?\n");
				r->del(r);
				p->del(p);
				if (errc != NULL) *errc = 4;
				return NULL;
			}
			r->del(r);
		}
	}

	/* Return the ramdac being used */
	if (p->or != NULL && cal != NULL) {
		ramdac *r;
		int j, i;
		
		if ((r = p->dw->get_ramdac(p->dw)) == NULL) {
			a1logd(log,1,"new_disprd failed becayse get_ramdac failed\n");
			a1logv(p->log, 1, "Failed to read current RAMDAC\n");
			p->del(p);
			if (errc != NULL) *errc = 4;
			return NULL;
		}
		/* Get the ramdac contents. */
		/* We linearly interpolate from RAMDAC[nent] to cal[ncal] resolution */
		for (i = 0; i < ncal; i++) {
			double val, w;
			unsigned int ix;

			val = (r->nent-1.0) * i/(ncal-1.0);
			ix = (unsigned int)floor(val);		/* Coordinate */
			if (ix > (r->nent-2))
				ix = (r->nent-2);
			w = val - (double)ix;		/* weight */
			for (j = 0; j < 3; j++) {
				val = r->v[j][ix];
				cal[j][i] = val + w * (r->v[j][ix+1] - val);
			}
		}
		r->del(r);
	}
	
	a1logd(log,1,"new_disprd succeeded\n");
	return p;
}

