
/* 
 * Argyll Color Management System
 *
 * Author: Graeme W. Gill
 * Date:   12/1/2009
 *
 * Copyright 2006 - 2014, Graeme W. Gill
 * All rights reserved.
 *
 * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
 * see the License2.txt file for licencing details.
 *
 * (Base on i1pro_imp.c)
 */

/* 
   If you make use of the instrument driver code here, please note
   that it is the author(s) of the code who are responsibility
   for its operation. Any problems or queries regarding driving
   instruments with the Argyll drivers, should be directed to
   the Argyll's author(s), and not to any other party.

   If there is some instrument feature or function that you
   would like supported here, it is recommended that you
   contact Argyll's author(s) first, rather than attempt to
   modify the software yourself, if you don't have firm knowledge
   of the instrument communicate protocols. There is a chance
   that an instrument could be damaged by an incautious command
   sequence, and the instrument companies generally cannot and
   will not support developers that they have not qualified
   and agreed to support.
 */

/* TTBD:

*/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#include <math.h>
#if defined(UNIX)
# include <utime.h>
#else
# include <sys/utime.h>
#endif
#include <sys/stat.h>
#ifndef SALONEINSTLIB
#include "copyright.h"
#include "aconfig.h"
#include "numlib.h"
#include "rspl.h"
#else /* SALONEINSTLIB */
#include <fcntl.h>
#include "sa_config.h"
#include "numsup.h"
#include "rspl1.h"
#endif /* SALONEINSTLIB */
#include "cgats.h"
#include "xspect.h"
#include "insttypes.h"
#include "conv.h"
#include "icoms.h"
#include "sort.h"

/* Configuration */
#undef USE_HIGH_GAIN_MODE /* [Und] Make use of high gain mode in emissive measurements */
#define USE_THREAD		/* [Def] Need to use thread, or there are 1.5 second internal */
						/* instrument delays ! */
#define ENABLE_NONVCAL	/* [Def] Enable saving calibration state between program runs to a file */
#define ENABLE_NONLINCOR	/* [Def] Enable non-linear correction */
						/* NOTE :- high gain scaling will be stuffed if disabled! */
#define ENABLE_LEDTEMPC	/* [Def] Enable LED temperature compensation */
#define ENABLE_BKDRIFTC	/* [Def] Enable Emis. Black drift compensation using sheilded cell values */
#define HEURISTIC_BKDRIFTC	/* [Def] Use heusristic black drift correction version */
#undef  ENABLE_REFSTRAYC	/* [Und] Enable Reflective stray light compensation */
#define REFSTRAYC_FACTOR 0.000086 /* [0.00043] Compensation factor */
#undef  ENABLE_REFLEDINTER	/* [Und] Enable Reflective LED interference correction */

#define ENABLE_SPOS_CHECK	/* [Def] Check the sensor position is reasonable for measurement */
#define FILTER_SPOS_EVENTS	/* [Def] Use a thread to filter SPOS event changes */
#define FILTER_TIME 500		/* [500] Filter time in msec */
#define DCALTOUT (1 * 60 * 60)		/* [1 Hrs] Dark Calibration timeout in seconds */
#define WCALTOUT (24 * 60 * 60)		/* [24 Hrs] White Calibration timeout in seconds */
#define MAXSCANTIME 20.0	/* [20 Sec] Maximum scan time in seconds */
#define SW_THREAD_TIMEOUT (10 * 60.0) 	/* [10 Min] Switch read thread timeout */

#define SINGLE_READ		/* [Def] Use a single USB read for scan to eliminate latency issues. */
#define HIGH_RES		/* [Def] Enable high resolution spectral mode code. Disable */
						/* to break dependency on rspl library. */
# undef FAST_HIGH_RES_SETUP	/* Slightly better accuracy ? */

/* Debug [Und] */
#undef DEBUG			/* Turn on extra messages & plots */
#undef PLOT_DEBUG		/* Use plot to show readings & processing */
#undef PLOT_REFRESH 	/* Plot refresh rate measurement info */
#undef PLOT_UPDELAY		/* Plot data used to determine display update delay */
#undef RAWR_DEBUG		/* Print out raw reading processing values */
#undef DUMP_SCANV		/* Dump scan readings to a file "mkdump.txt" */
#undef DUMP_DARKM		/* Append raw dark readings to file "mkddump.txt" */
#undef DUMP_BKLED		/* Save REFSTRAYC & REFLEDNOISE comp plot to "refbk1.txt" & "refbk2.txt" */
#undef APPEND_MEAN_EMMIS_VAL /* Append averaged uncalibrated reading to file "mkdump.txt" */
#undef IGNORE_WHITE_INCONS	/* Ignore define reference reading inconsistency */
#undef TEST_DARK_INTERP	/* Test out the dark interpolation (need DEBUG for plot) */
#undef PLOT_RCALCURVE	/* Plot the reflection reference curve */
#undef PLOT_ECALCURVES	/* Plot the emission reference curves */
#undef PLOT_TEMPCOMP	/* Plot before and after LED temp. compensation */
#define PATREC_DEBUG			/* [und] Print & Plot & opt. Log patch/flash recognition information */
#undef PATREC_LOG			/* [und] Log PATREC debugging to patchrec.plot file */
#define PATREC_LOG_LEVEL 6	/* [5]   PATREC logging debugging level */
#undef PATREC_PLOT_ALLBANDS	/* Plot all bands of scan */
#undef HIGH_RES_DEBUG
#undef HIGH_RES_PLOT
#undef HIGH_RES_PLOT_STRAYL		/* Plot stray light upsample */
#undef FAKE_EEPROM				/* Get [und] EEPROM data from munki_fake_eeprom.h */

#define DISP_INTT 0.7			/* Seconds per reading in display spot mode */
								/* More improves repeatability in dark colors, but limits */
								/* the maximum brightness level befor saturation. */
								/* A value of 2.0 seconds has a limit of about 110 cd/m^2 */
#define DISP_INTT2 0.3			/* High brightness display spot mode seconds per reading, */
								/* Should be good up to 275 cd/m^2 */
#define DISP_INTT3 0.1			/* High brightness display spot mode seconds per reading, */
								/* Should be good up to 700 cd/m^2 */

#define ADARKINT_MIN 0.01		/* Min cal time for adaptive dark cal */
#define ADARKINT_MAX 2.0		/* Max cal time for adaptive dark cal with high gain mode */
#define ADARKINT_MAX2 4.0		/* Max cal time for adaptive dark for no high gain */

#define RDEAD_TIME 0.004432		/* Fudge figure to make reflecting intn. time scale linearly */
					
#define EMIS_SCALE_FACTOR 1.0	/* Emission mode scale factor */ 
#define AMB_SCALE_FACTOR 1.0	/* Ambient mode scale factor for Lux */

#define NSEN_MAX 140            /* Maximum nsen/raw value we can cope with */

/* Wavelength to start duplicating values below, because it is too noisy */
#define WL_REF_MIN 420.0
#define WL_EMIS_MIN 400.0

/* High res mode settings */
#define HIGHRES_SHORT 380		/* Wavelength to calculate. Too noisy to try expanding range. */
#define HIGHRES_LONG  730
#define HIGHRES_WIDTH  (10.0/3.0) /* (The 3.3333 spacing and lanczos2 seems a good combination) */


#include "munki.h"
#include "munki_imp.h"
#include "xrga.h"

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

#define PATCH_CONS_THR 0.05		/* Dark measurement consistency threshold */
#define DARKTHSCAMIN 5000.0		/* Dark threshold scaled/offset minimum */

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

/* Three levels of runtime debugging messages:

	~~~ this is no longer accurate. a1logd calls
	~~~ probably need to be tweaked.
   1 = default, typical I/O messages etc.
   2 = more internal operation messages
   3 = dump extra detailes
   4 = dump EEPROM data
   5 = dump tables etc
*/

/* ============================================================ */
/* Debugging plot support */

#if defined(DEBUG) || defined(PLOT_DEBUG) || defined(PATREC_DEBUG) || defined(HIGH_RES_PLOT) ||  defined(HIGH_RES_PLOT_STRAYL)

# include <plot.h>

static int disdebplot = 0;

# define DISDPLOT disdebplot = 1;
# define ENDPLOT disdebplot = 0;

/* Plot a CCD spectra */
static void plot_raw(double *data) {
	int i;
	double xx[NSEN_MAX];
	double yy[NSEN_MAX];

	if (disdebplot)
		return;

	for (i = 0; i < 128; i++) {
		xx[i] = (double)i;
		yy[i] = data[i];
	}
	do_plot(xx, yy, NULL, NULL, 128);
}

/* Plot two CCD spectra */
static void plot_raw2(double *data1, double *data2) {
	int i;
	double xx[NSEN_MAX];
	double y1[NSEN_MAX];
	double y2[NSEN_MAX];

	if (disdebplot)
		return;

	for (i = 0; i < 128; i++) {
		xx[i] = (double)i;
		y1[i] = data1[i];
		y2[i] = data2[i];
	}
	do_plot(xx, y1, y2, NULL, 128);
}

/* Plot a converted spectra */
static void plot_wav(munkiimp *m, double *data) {
	int i;
	double xx[NSEN_MAX];
	double yy[NSEN_MAX];

	if (disdebplot)
		return;

	for (i = 0; i < m->nwav; i++) {
		xx[i] = XSPECT_WL(m->wl_short, m->wl_long, m->nwav, i);
		yy[i] = data[i];
	}
	do_plot(xx, yy, NULL, NULL, m->nwav);
}

/* Plot a standard res spectra */
static void plot_wav1(munkiimp *m, double *data) {
	int i;
	double xx[36];
	double yy[36];

	if (disdebplot)
		return;

	for (i = 0; i < m->nwav1; i++) {
		xx[i] = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, i);
		yy[i] = data[i];
	}
	do_plot(xx, yy, NULL, NULL, m->nwav1);
}

/* Plot a high res spectra */
static void plot_wav2(munkiimp *m, double *data) {
	int i;
	double xx[NSEN_MAX];
	double yy[NSEN_MAX];

	if (disdebplot)
		return;

	for (i = 0; i < m->nwav2; i++) {
		xx[i] = XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, i);
		yy[i] = data[i];
	}
	do_plot(xx, yy, NULL, NULL, m->nwav2);
}

#else	/* !PLOT_DEBUG */

# define DISDPLOT 
# define ENDPLOT 

#endif	/* !PLOT_DEBUG */

/* ============================================================ */

munki_code munki_touch_calibration(munki *p);

/* Implementation struct */

/* Add an implementation structure */
munki_code add_munkiimp(munki *p) {
	munkiimp *m;

	if ((m = (munkiimp *)calloc(1, sizeof(munkiimp))) == NULL) {
		a1logd(p->log,3,"add_munkiimp malloc %lu bytes failed (1)\n",sizeof(munkiimp));
		return MUNKI_INT_MALLOC;
	}
	m->p = p;

	m->lo_secs = 2000000000;        /* A very long time */

	p->m = (void *)m;
	return MUNKI_OK;
}

/* Shutdown instrument, and then destroy */
/* implementation structure */
void del_munkiimp(munki *p) {

	a1logd(p->log,3,"munki_del called\n");

#ifdef ENABLE_NONVCAL
	/* Touch it so that we know when the instrument was last open */
	munki_touch_calibration(p);
#endif /* ENABLE_NONVCAL */

	if (p->m != NULL) {
		int i;
		munkiimp *m = (munkiimp *)p->m;
		munki_state *s;

#ifdef FILTER_SPOS_EVENTS
		if (m->spos_th != NULL)
			m->spos_th_term = 1;
#endif
		
		if (m->th != NULL) {		/* Terminate switch monitor thread by simulating an event */
			m->th_term = 1;			/* Tell thread to exit on error */
			munki_simulate_event(p, mk_eve_spos_change, 0);
			for (i = 0; m->th_termed == 0 && i < 5; i++)
				msec_sleep(50);	/* Wait for thread to terminate */
			if (i >= 5) {
				a1logd(p->log,3,"Munki switch thread termination failed\n");
				m->th->terminate(m->th);	/* Try and force thread to terminate */
			}
			m->th->del(m->th);
			usb_uninit_cancel(&m->sw_cancel);	/* Don't need cancel token now */
		}

#ifdef FILTER_SPOS_EVENTS
		if (m->spos_th != NULL) {
			for (i = 0; m->spos_th_termed == 0 && i < 5; i++)
				msec_sleep(50);	/* Wait for thread to terminate */
			if (i >= 5) {
				a1logd(p->log,3,"Munki spos thread termination failed\n");
				m->spos_th->terminate(m->spos_th);	/* Try and force thread to terminate */
			}
			m->spos_th->del(m->spos_th);
		}
#endif

		/* Free any per mode data */
		for (i = 0; i < mk_no_modes; i++) {
			s = &m->ms[i];

			free_dvector(s->dark_data, -1, m->nraw-1);  
			free_dvector(s->dark_data2, -1, m->nraw-1);  
			free_dvector(s->dark_data3, -1, m->nraw-1);  
			free_dvector(s->white_data, -1, m->nraw-1);
			free_dmatrix(s->iwhite_data, 0, 1, -1, m->nraw-1);  
			free_dmatrix(s->idark_data, 0, 3, -1, m->nraw-1);  

			free_dvector(s->cal_factor1, 0, m->nwav1-1);
			free_dvector(s->cal_factor2, 0, m->nwav2-1);
		}

		/* Free arrays */

		if (m->lin0 != NULL)
			free(m->lin0);
		if (m->lin1 != NULL)
			free(m->lin1);

		if (m->white_ref1 != NULL)
			free(m->white_ref1);
		if (m->emis_coef1 != NULL)
			free(m->emis_coef1);
		if (m->amb_coef1 != NULL)
			free(m->amb_coef1);
		if (m->proj_coef1 != NULL)
			free(m->proj_coef1);

		if (m->white_ref2 != NULL)
			free(m->white_ref2);
		if (m->emis_coef2 != NULL)
			free(m->emis_coef2);
		if (m->amb_coef2 != NULL)
			free(m->amb_coef2);
		if (m->proj_coef2 != NULL)
			free(m->proj_coef2);

		if (m->straylight1 != NULL)
			free_dmatrix(m->straylight1, 0, m->nwav1-1, 0, m->nwav1-1);  

		if (m->straylight2 != NULL)
			free_dmatrix(m->straylight2, 0, m->nwav1-2, 0, m->nwav1-2);  

		if (m->rmtx_index1 != NULL)
			free(m->rmtx_index1);
		if (m->rmtx_nocoef1 != NULL)
			free(m->rmtx_nocoef1);
		if (m->rmtx_coef1 != NULL)
			free(m->rmtx_coef1);

		if (m->rmtx_index2 != NULL)
			free(m->rmtx_index2);
		if (m->rmtx_nocoef2 != NULL)
			free(m->rmtx_nocoef2);
		if (m->rmtx_coef2 != NULL)
			free(m->rmtx_coef2);

		if (m->emtx_index1 != NULL)
			free(m->emtx_index1);
		if (m->emtx_nocoef1 != NULL)
			free(m->emtx_nocoef1);
		if (m->emtx_coef1 != NULL)
			free(m->emtx_coef1);

		if (m->emtx_index2 != NULL)
			free(m->emtx_index2);
		if (m->emtx_nocoef2 != NULL)
			free(m->emtx_nocoef2);
		if (m->emtx_coef2 != NULL)
			free(m->emtx_coef2);

		free(m);
		p->m = NULL;
	}
}

/* ============================================================ */
/* Little endian wire format conversion routines */

/* Take an int, and convert it into a byte buffer little endian */
static void int2buf(unsigned char *buf, int inv) {
	buf[3] = (inv >> 24) & 0xff;
	buf[2] = (inv >> 16) & 0xff;
	buf[1] = (inv >> 8) & 0xff;
	buf[0] = (inv >> 0) & 0xff;
}

/* Take a short, and convert it into a byte buffer little endian */
static void short2buf(unsigned char *buf, int inv) {
	buf[1] = (inv >> 8) & 0xff;
	buf[0] = (inv >> 0) & 0xff;
}

/* Take a word sized buffer, and convert it to an int */
static int buf2int(unsigned char *buf) {
	int val;
	val =      ((signed char *)buf)[3];
	val = ((val << 8) + (0xff & buf[2]));
	val = ((val << 8) + (0xff & buf[1]));
	val = ((val << 8) + (0xff & buf[0]));
	return val;
}

/* Take a word sized buffer, and convert it to an unsigned int */
static unsigned int buf2uint(unsigned char *buf) {
	unsigned int val;
	val =               (0xff & buf[3]);
	val = ((val << 8) + (0xff & buf[2]));
	val = ((val << 8) + (0xff & buf[1]));
	val = ((val << 8) + (0xff & buf[0]));
	return val;
}

/* Take a short sized buffer, and convert it to an int */
static int buf2short(unsigned char *buf) {
	int val;
	val =      ((signed char *)buf)[1];
	val = ((val << 8) + (0xff & buf[0]));
	return val;
}

/* Take a unsigned short sized buffer, and convert it to an int */
static int buf2ushort(unsigned char *buf) {
	int val;
	val =               (0xff & buf[1]);
	val = ((val << 8) + (0xff & buf[0]));
	return val;
}

/* ============================================================ */
/* High level functions */

#ifdef FAKE_EEPROM
# pragma message("######### munki_imp.c FAKE EEPROM compiled !!!!! ########")
# include "munki_fake_eeprom.h"
#endif

/* Initialise our software state from the hardware */
munki_code munki_imp_init(munki *p) {
	munkiimp *m = (munkiimp *)p->m;
	munki_code ev = MUNKI_OK;
	unsigned char buf[4];
	int calsize = 0, rucalsize;
	unsigned char *calbuf;	/* EEProm contents */
	char *envv;

	a1logd(p->log,2,"munki_init:\n");

	if (p->dtype != instColorMunki)
		return MUNKI_UNKNOWN_MODEL;


	m->native_calstd = xcalstd_xrga;
	m->target_calstd = xcalstd_native;		/* Default to native calibration standard*/

	/* Honour Environment override */
	if ((envv = getenv("ARGYLL_XCALSTD")) != NULL) {
		if (strcmp(envv, "XRGA") == 0)
			m->target_calstd = xcalstd_xrga;
		else if (strcmp(envv, "XRDI") == 0)
			m->target_calstd = xcalstd_xrdi;
		else if (strcmp(envv, "GMDI") == 0)
			m->target_calstd = xcalstd_gmdi;
	}

#ifdef ENABLE_SPOS_CHECK
	m->nosposcheck = 0;
#else
# pragma message("####### ColorMunki Sensor Position Check is OFF! ########")
	m->nosposcheck = 1;
#endif

	m->trig = inst_opt_trig_user;
	m->scan_toll_ratio = 1.0;

	/* Get the firmware parameters so that we can check eeprom range. */
	if ((ev = munki_getfirm(p, &m->fwrev, &m->tickdur, &m->minintcount, &m->noeeblocks, &m->eeblocksize)) != MUNKI_OK)
		return ev; 
	a1logd(p->log,2,"Firmware rev = %d.%d\n",m->fwrev/256, m->fwrev % 256);

	/* Check the EEProm */
	if (m->noeeblocks != 2 || m->eeblocksize != 8192) {
		a1logw(p->log,"EEProm is unexpected size\n");
		return MUNKI_INT_ASSERT;
	}

	/* Tick in seconds */
	m->intclkp = (double)m->tickdur * 1e-6;

	/* Set these to reasonable values */
	m->min_int_time = m->intclkp * (double)m->minintcount;
	m->max_int_time = 4.5;

	a1logd(p->log,3, "minintcount %d, min_int_time = %f\n", m->minintcount, m->min_int_time);

	/* Get the sensor Chip ID */
	if ((ev = munki_getchipid(p, m->chipid)) != MUNKI_OK)
		return ev; 

	/* Get the Version String */
	if ((ev = munki_getversionstring(p, m->vstring)) != MUNKI_OK)
		return ev; 

	/* Dump the eeprom contents as a block */
	if (p->log->debug >= 9) {
		int base, size;
	
		a1logd(p->log,7, "EEPROM contents:\n"); 

		size = 1024;	/* read size == buffer size */
		for (base = 0; base < (2 * 8192); base += size) {
			unsigned char eeprom[1024];

			if ((ev = munki_readEEProm(p, eeprom, base, size)) != MUNKI_OK)
				return ev;
		
			adump_bytes(p->log, "  ", eeprom, base, size);
		}
	}

	/* Read the calibration size */
	if ((ev = munki_readEEProm(p, buf, 4, 4)) != MUNKI_OK)
		return ev;
	calsize = buf2int(buf);
	rucalsize = (calsize + 3) & ~3;	/* Round up to next 32 bits */
	
	if (calsize < 12)
		return MUNKI_INT_CALTOOSMALL;
	if (calsize > (m->noeeblocks * m->eeblocksize))
		return MUNKI_INT_CALTOOBIG;

	/* Read the calibration raw data from the EEProm */
	if ((calbuf = (unsigned char *)calloc(rucalsize, sizeof(unsigned char))) == NULL) {
		a1logd(p->log,3,"munki_imp_init malloc %d bytes failed\n",rucalsize);
		return MUNKI_INT_MALLOC;
	}

#ifdef FAKE_EEPROM
	if (calsize != FAKE_EEPROM_SIZE) {
		fprintf(stderr,"Fake EEPROM size %d != expected %d\n",FAKE_EEPROM_SIZE,calsize);
		return -1;
	}
	memcpy(calbuf, fake_eeprom_data, calsize);
#else
	if ((ev = munki_readEEProm(p, calbuf, 0, calsize)) != MUNKI_OK)
		return ev;
#endif

	if ((ev = munki_parse_eeprom(p, calbuf, rucalsize)) != MUNKI_OK)
		return ev;

	free(calbuf);
	calbuf = NULL;

#ifdef USE_THREAD
	/* Setup the switch monitoring thread */
	usb_init_cancel(&m->sw_cancel);			/* Get cancel token ready */
	if ((m->th = new_athread(munki_switch_thread, (void *)p)) == NULL)
		return MUNKI_INT_THREADFAILED;
#endif

#ifdef FILTER_SPOS_EVENTS
	/* Setup the sensor position filter thread */
	if ((m->spos_th = new_athread(munki_spos_thread, (void *)p)) == NULL)
		return MUNKI_INT_THREADFAILED;
#endif

	/* Set up the current state of each mode */
	{
		int i, j;
		munki_state *s;

		/* First set state to basic configuration */
		for (i = 0; i < mk_no_modes; i++) {
			s = &m->ms[i];

			s->mode = i;

			/* Default to an emissive configuration */
			s->targoscale = 0.90;	/* Allow extra 10% margine by default */
			s->targmaxitime = 2.0;	/* Maximum integration time to aim for */
			s->targoscale2 = 0.15;	/* Proportion of targoscale to meed targmaxitime */

#ifdef USE_HIGH_GAIN_MODE
			s->auto_gain = 1;		/* No high gain by default */
#else
			s->auto_gain = 0;		/* No high gain by default */
#endif
			s->gainmode = 0;		/* Normal gain mode */
			s->inttime = 0.5;		/* Initial integration time */


			s->dark_valid = 0;		/* Dark cal invalid */
			s->dark_data = dvectorz(-1, m->nraw-1);  
			s->dark_data2 = dvectorz(-1, m->nraw-1);  
			s->dark_data3 = dvectorz(-1, m->nraw-1);  

			s->cal_valid = 0;		/* Scale cal invalid */
			s->cal_factor1 = dvectorz(0, m->nwav1-1);
			s->cal_factor2 = dvectorz(0, m->nwav2-1);
			s->cal_factor = s->cal_factor1; /* Default to standard resolution */
			s->white_data = dvectorz(-1, m->nraw-1);
			s->iwhite_data = dmatrixz(0, 1, -1, m->nraw-1);  

			s->idark_valid = 0;		/* Interpolatable Dark cal invalid */
			s->idark_data = dmatrixz(0, 3, -1, m->nraw-1);  

			s->dark_int_time  = DISP_INTT;	/* 0.7 */
			s->dark_int_time2 = DISP_INTT2;	/* 0.3 */
			s->dark_int_time3 = DISP_INTT3;	/* 0.1 */

			s->idark_int_time[0] = s->idark_int_time[2] = m->min_int_time;
			s->idark_int_time[1] = ADARKINT_MAX; /* 2.0 */
			s->idark_int_time[3] = ADARKINT_MAX2; /* 4.0 */

			s->want_calib = 1;		/* By default want an initial calibration */
			s->want_dcalib = 1;
		}

		/* Then add mode specific settings */
		for (i = 0; i < mk_no_modes; i++) {
			s = &m->ms[i];
			switch(i) {
				case mk_refl_spot:
					s->auto_gain = 0;			/* Don't automatically set gain */
					s->targoscale = 1.0;		/* Optimised sensor scaling to full */
					s->reflective = 1;
					s->adaptive = 0;
					s->inttime = s->targoscale * m->cal_int_time;
					s->dark_int_time = s->inttime;

					s->dpretime = 0.20;			/* Pre-measure time */
					s->wpretime = 0.20;
					s->dcaltime = 0.5;			/* same as reading */
					s->wcaltime = 0.5;			/* same as reading */
					s->dreadtime = 0.5;			/* same as reading */
					s->wreadtime = 0.5;
					s->maxscantime = 0.0;
					break;

				case mk_refl_scan:
					s->auto_gain = 0;				/* Don't automatically set gain */
					s->targoscale = 1.0;			/* Maximize level */
					s->reflective = 1;
					s->scan = 1;
					s->adaptive = 0;
//					s->inttime = (s->targoscale * m->cal_int_time - RDEAD_TIME) + RDEAD_TIME;
//					if (s->inttime < m->min_int_time)
//						s->inttime = m->min_int_time;
//					s->dark_int_time = s->inttime;
					s->inttime = s->targoscale * m->cal_int_time;
					s->dark_int_time = s->inttime;

					s->dpretime = 0.20;		/* Pre-measure time */
					s->wpretime = 0.20;
					s->dcaltime = 0.5;
					s->wcaltime = 0.5;
					s->dreadtime = 0.10;
					s->wreadtime = 0.10;
					s->maxscantime = MAXSCANTIME;
					break;

				case mk_emiss_spot_na:			/* Emissive spot not adaptive */
				case mk_tele_spot_na:			/* Tele spot not adaptive */
					s->targoscale = 0.90;		/* Allow extra 10% margine */
					if (i == mk_emiss_spot_na) {
						for (j = 0; j < m->nwav1; j++)
							s->cal_factor1[j] = EMIS_SCALE_FACTOR * m->emis_coef1[j];
					} else {
						for (j = 0; j < m->nwav1; j++)
							s->cal_factor1[j] = EMIS_SCALE_FACTOR * m->proj_coef1[j];
						s->projector = 1;
					}
					s->cal_valid = 1;
					s->emiss = 1;
					s->adaptive = 0;

					s->inttime = DISP_INTT;		/* Default disp integration time (ie. 0.7 sec) */
					s->dark_int_time = s->inttime;
					s->dark_int_time2 = DISP_INTT2;	/* Alternate disp integration time (ie. 0.3) */
					s->dark_int_time3 = DISP_INTT3;	/* Alternate disp integration time (ie. 0.1) */

					s->dpretime = 0.0;
					s->wpretime = 0.20;
					s->dcaltime = 1.0;		/* ie. determines number of measurements */
					s->dcaltime2 = 1.0;
					s->dcaltime3 = 1.0;
					s->wcaltime = 0.0;
					s->dreadtime = 0.0;
					s->wreadtime = DISP_INTT;
					s->maxscantime = 0.0;
					break;

				case mk_emiss_spot:
				case mk_amb_spot:
				case mk_tele_spot:				/* Adaptive projector */
					s->targoscale = 0.90;		/* Allow extra 5% margine */
					if (i == mk_emiss_spot) {
						for (j = 0; j < m->nwav1; j++)
							s->cal_factor1[j] = EMIS_SCALE_FACTOR * m->emis_coef1[j];
					} else if (i == mk_amb_spot) {
						for (j = 0; j < m->nwav1; j++)
							s->cal_factor1[j] = AMB_SCALE_FACTOR * m->amb_coef1[j];
						s->ambient = 1;
					} else {
						for (j = 0; j < m->nwav1; j++)
							s->cal_factor1[j] = EMIS_SCALE_FACTOR * m->proj_coef1[j];
						s->projector = 1;
					}

					s->cal_valid = 1;
					s->emiss = 1;
					s->adaptive = 1;

					s->dpretime = 0.0;
					s->wpretime = 0.10;
					s->dcaltime = 1.0;
					s->wcaltime = 0.0;
					s->dreadtime = 0.0;
					s->wreadtime = 1.0;
					s->maxscantime = 0.0;
					break;

				case mk_emiss_scan:
				case mk_amb_flash:
					s->targoscale = 0.90;		/* Allow extra 10% margine */
					if (i == mk_emiss_scan) {
						for (j = 0; j < m->nwav1; j++)
							s->cal_factor1[j] = EMIS_SCALE_FACTOR * m->emis_coef1[j];
					} else {
						for (j = 0; j < m->nwav1; j++)
							s->cal_factor1[j] = AMB_SCALE_FACTOR * m->amb_coef1[j];
						s->ambient = 1;
						s->flash = 1;
					}
					s->cal_valid = 1;
					s->emiss = 1;
					s->scan = 1;
					s->adaptive = 0;
					s->inttime = m->min_int_time;
					s->dark_int_time = s->inttime;

					s->dpretime = 0.0;
					s->wpretime = 0.10;
					s->dcaltime = 1.0;
					s->wcaltime = 0.0;
					s->dreadtime = 0.0;
					s->wreadtime = 0.10;
					s->maxscantime = MAXSCANTIME;
					break;

				/* Transparency has a white reference, and interpolated dark ref. */
				case mk_trans_spot:
					s->targoscale = 0.90;		/* Allow extra 10% margine */
					s->trans = 1;
					s->adaptive = 1;

					s->dpretime = 0.20;
					s->wpretime = 0.20;
					s->dcaltime = 1.0;
					s->wcaltime = 1.0;
					s->dreadtime = 0.0;
					s->wreadtime = 1.0;
					s->maxscantime = 0.0;
					break;

				case mk_trans_scan:
					// ~~99 should we use high gain mode ??
					s->targoscale = 0.10;		/* Scan as fast as possible */
					s->trans = 1;
					s->scan = 1;
					s->inttime = s->targoscale * m->cal_int_time;
					if (s->inttime < m->min_int_time)
						s->inttime = m->min_int_time;
					s->dark_int_time = s->inttime;
					s->adaptive = 0;

					s->dpretime = 0.20;
					s->wpretime = 0.20;
					s->dcaltime = 1.0;
					s->wcaltime = 1.0;
					s->dreadtime = 0.00;
					s->wreadtime = 0.10;
					s->maxscantime = MAXSCANTIME;
					break;
			}
		}
	}

#ifdef ENABLE_NONVCAL
	/* Restore the all modes calibration from the local system */
	munki_restore_calibration(p);
	/* Touch it so that we know when the instrument was last opened */
	munki_touch_calibration(p);
#endif
	
	a1logv(p->log, 1, 
		"Instrument Type:   ColorMunki\n"	// ~~ should get this from version string ?
		"Serial Number:     %s\n"
		"Firmware version:  %d\n"
		"Chip ID:           %02X-%02X%02X%02X%02X%02X%02X%02X\n"
		"Version string:    '%s'\n"
		"Calibration Ver.:  %d\n"
		"Production No.:    %d\n",
		m->serno,
		m->fwrev,
	    m->chipid[0], m->chipid[1], m->chipid[2], m->chipid[3],
		m->chipid[4], m->chipid[5], m->chipid[6], m->chipid[7],
		m->vstring,
		m->calver,
		m->prodno);

	/* Flash the LED, just cos we can! */
	if ((ev = munki_setindled(p, 1000,0,0,-1,0)) != MUNKI_OK)
		return ev;
	msec_sleep(200);
	if ((ev = munki_setindled(p, 0,0,0,0,0)) != MUNKI_OK)
		return ev;

	return ev;
}

/* Return a pointer to the serial number */
char *munki_imp_get_serial_no(munki *p) {
	munkiimp *m = (munkiimp *)p->m;

	return m->serno; 
}

/* Set the measurement mode. It may need calibrating */
munki_code munki_imp_set_mode(
	munki *p,
	mk_mode mmode,		/* Operating mode */
	inst_mode mode		/* Full mode mask for options */
) {
	munkiimp *m = (munkiimp *)p->m;

	a1logd(p->log,2,"munki_imp_set_mode called with mode no. %d and mask 0x%x\n",mmode,m);
	switch(mmode) {
		case mk_refl_spot:
		case mk_refl_scan:
		case mk_emiss_spot_na:
		case mk_tele_spot_na:
		case mk_emiss_spot:
		case mk_tele_spot:
		case mk_emiss_scan:
		case mk_amb_spot:
		case mk_amb_flash:
		case mk_trans_spot:
		case mk_trans_scan:
			m->mmode = mmode;
			break;
		default:
			return MUNKI_INT_ILLEGALMODE;
	}
	m->spec_en = (mode & inst_mode_spectral) != 0;

	if ((mode & inst_mode_highres) != 0) {
		munki_code rv;
		if ((rv = munki_set_highres(p)) != MUNKI_OK)
			return rv;
	} else {
		munki_set_stdres(p);	/* Ignore any error */
	}

	return MUNKI_OK;
}

/* Return needed and available inst_cal_type's */
munki_code munki_imp_get_n_a_cals(munki *p, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
	munkiimp *m = (munkiimp *)p->m;
	munki_state *cs = &m->ms[m->mmode];
	time_t curtime = time(NULL);
	inst_cal_type n_cals = inst_calt_none;
	inst_cal_type a_cals = inst_calt_none;
	int idark_valid = cs->idark_valid;			/* Current state of calib */
	int dark_valid = cs->dark_valid;
	int cal_valid = cs->cal_valid;

	a1logd(p->log,3,"munki_imp_get_n_a_cals: checking mode %d\n",m->mmode);

	/* Timeout calibrations that are too old */
	a1logd(p->log,4,"curtime %u, iddate %u, ddate %u, cfdate %u\n",curtime,cs->iddate,cs->ddate,cs->cfdate);
	if ((curtime - cs->iddate) > DCALTOUT) {
		a1logd(p->log,3,"Invalidating adaptive dark cal as %d secs from last cal\n",curtime - cs->iddate);
		idark_valid = 0;
	}
	if ((curtime - cs->ddate) > DCALTOUT) {
		a1logd(p->log,3,"Invalidating dark cal as %d secs from last cal\n",curtime - cs->ddate);
		dark_valid = 0;
	}
	if (!cs->emiss && (curtime - cs->cfdate) > WCALTOUT) {
		a1logd(p->log,3,"Invalidating white cal as %d secs from last cal\n",curtime - cs->cfdate);
		cal_valid = 0;
	}

	if (cs->reflective) {
		if (!dark_valid
		 || (cs->want_dcalib && !m->noinitcalib))
			n_cals |= inst_calt_ref_dark;
		a_cals |= inst_calt_ref_dark;

		if (!cal_valid
		 || (cs->want_calib && !m->noinitcalib))
			n_cals |= inst_calt_ref_white;
		a_cals |= inst_calt_ref_white;
	}
	if (cs->emiss) {
		if ((!cs->adaptive && !dark_valid)
		 || (cs->adaptive && !idark_valid)
		 || (cs->want_dcalib && !m->noinitcalib))
			n_cals |= inst_calt_em_dark;
		a_cals |= inst_calt_em_dark;
	}
	if (cs->trans) {
		if ((!cs->adaptive && !dark_valid)
		 || (cs->adaptive && !idark_valid)
	     || (cs->want_dcalib && !m->noinitcalib))
			n_cals |= inst_calt_trans_dark;
		a_cals |= inst_calt_trans_dark;

		if (!cal_valid
	     || (cs->want_calib && !m->noinitcalib))
			n_cals |= inst_calt_trans_vwhite;
		a_cals |= inst_calt_trans_vwhite;
	}
	if (cs->emiss && !cs->scan && !cs->adaptive) {
		if (!cs->done_dintsel)
			n_cals |= inst_calt_emis_int_time;
		a_cals |= inst_calt_emis_int_time;
	}

	if (pn_cals != NULL)
		*pn_cals = n_cals;

	if (pa_cals != NULL)
		*pa_cals = a_cals;

	a1logd(p->log,3,"munki_imp_get_n_a_cals: returning n_cals 0x%x, a_cals 0x%x\n",n_cals, a_cals);

	return MUNKI_OK;
}

/* - - - - - - - - - - - - - - - - */
/* Calibrate for the current mode. */
/* Request an instrument calibration of the current mode. */
munki_code munki_imp_calibrate(
	munki *p,
	inst_cal_type *calt,	/* Calibration type to do/remaining */
	inst_cal_cond *calc,	/* Current condition/desired condition */
	inst_calc_id_type *idtype,	/* Condition identifier type */
	char id[CALIDLEN]		/* Condition identifier (ie. white reference ID) */
) {
	munki_code ev = MUNKI_OK;
	munkiimp *m = (munkiimp *)p->m;
	int mmode = m->mmode;					/* Current actual mode */
	munki_state *cs = &m->ms[m->mmode];
	int sx1, sx2, sx;
	time_t cdate = time(NULL);
	int nummeas = 0;
	mk_spos spos;
	int i, j, k;
	inst_cal_type needed, available;

	a1logd(p->log,3,"munki_imp_calibrate called with calt 0x%x, calc 0x%x\n",*calt, *calc);

	if ((ev = munki_imp_get_n_a_cals(p, &needed, &available)) != MUNKI_OK)
		return ev;

	/* Translate inst_calt_all/needed into something specific */
	if (*calt == inst_calt_all
	 || *calt == inst_calt_needed
	 || *calt == inst_calt_available) {
		if (*calt == inst_calt_all) 
			*calt = (needed & inst_calt_n_dfrble_mask) | inst_calt_ap_flag;
		else if (*calt == inst_calt_needed)
			*calt = needed & inst_calt_n_dfrble_mask;
		else if (*calt == inst_calt_available)
			*calt = available & inst_calt_n_dfrble_mask;

		a1logd(p->log,4,"munki_imp_calibrate: doing calt 0x%x\n",*calt);

		if ((*calt & inst_calt_n_dfrble_mask) == 0)		/* Nothing todo */
			return MUNKI_OK;
	}

	/* See if it's a calibration we understand */
	if (*calt & ~available & inst_calt_all_mask) { 
		return MUNKI_UNSUPPORTED;
	}

	/* Get current sensor position */
	if ((ev = munki_getstatus(p, &spos, NULL)) != MUNKI_OK) {
		return ev;
	}
	a1logd(p->log,4,"munki sensor position = 0x%x\n",spos);

	/* We can set the *calc to the actual conditions, in which case */
	/* the calibration will commence immediately. */
	if (m->nocalibask && !m->nosposcheck && spos == mk_spos_calib) {
		*calc = inst_calc_man_cal_smode;
		a1logd(p->log,4,"munki set calc to cal conditions\n",spos);
	}

	/* Make sure that the instrument configuration matches the */
	/* conditions */
	if ((*calc & inst_calc_cond_mask) == inst_calc_man_cal_smode) {
		if (!m->nosposcheck && spos != mk_spos_calib) {
			return MUNKI_SPOS_CALIB;
		}
	} else if ((*calc & inst_calc_cond_mask) == inst_calc_man_trans_white) {
		if (!m->nosposcheck && spos != mk_spos_surf) {
			return MUNKI_SPOS_SURF;
		}
	}

	a1logd(p->log,4,"munki_imp_calibrate has right conditions\n");

	if (*calt & inst_calt_ap_flag) {
		sx1 = 0; sx2 = mk_no_modes;		/* Go through all the modes */
	} else {
		sx1 = m->mmode; sx2 = sx1 + 1;		/* Just current mode */
	}

	/* Go through the modes we are going to cover */
	for (sx = sx1; sx < sx2; sx++) {
		munki_state *s = &m->ms[sx];
		m->mmode = sx;				/* A lot of functions we call rely on this */

		a1logd(p->log,3,"\nCalibrating mode %d\n", s->mode);

		/* Sanity check scan mode settings, in case something strange */
		/* has been restored from the persistence file. */
//		if (s->scan && s->inttime > (2.1 * m->min_int_time)) {
//			s->inttime = m->min_int_time;	/* Maximize scan rate */
//		}
	
		/* We are now either in inst_calc_man_cal_smode, */ 
		/* inst_calc_man_trans_white, inst_calc_disp_white or inst_calc_proj_white */
		/* sequenced in that order, and in the appropriate condition for it. */
	
		/* Fixed int. time black calibration: */
		/* Reflective uses on the fly black, even for adaptive. */
		/* Emiss and trans can use single black ref only for non-adaptive */
		/* using the current inttime & gainmode, while display mode */
		/* does an extra fallback black cal for bright displays. */
		if ((*calt & (inst_calt_ref_dark
		            | inst_calt_em_dark
		            | inst_calt_trans_dark | inst_calt_ap_flag))
		 && (*calc & inst_calc_cond_mask) == inst_calc_man_cal_smode
		 && ( s->reflective
		  || (s->emiss && !s->adaptive && !s->scan)
		  || (s->trans && !s->adaptive))) {
			int stm;
			int usesdct23 = 0;			/* Is a mode that uses dcaltime2 & 3 */

			if (s->emiss && !s->adaptive && !s->scan)
				usesdct23 = 1;
	
			nummeas = munki_comp_nummeas(p, s->dcaltime, s->inttime);
	
			a1logd(p->log,3,"\nDoing initial black calibration with dcaltime %f, int_time %f, nummeas %d, gainmode %d\n", s->dcaltime, s->inttime, nummeas, s->gainmode);
			stm = msec_time();
			if ((ev = munki_dark_measure(p, s->dark_data, nummeas, &s->inttime, s->gainmode))
		                                                                         != MUNKI_OK) {
				m->mmode = mmode;           /* Restore actual mode */
				return ev;
			}
			a1logd(p->log,4,"Execution time of dark calib time %f sec = %d msec\n",s->inttime,msec_time() - stm);
	
			/* Special display mode alternate integration time black measurement */
			if (usesdct23) {
				nummeas = munki_comp_nummeas(p, s->dcaltime2, s->dark_int_time2);
				a1logd(p->log,3,"Doing 2nd initial black calibration with dcaltime2 %f, dark_int_time2 %f, nummeas %d, gainmode %d\n", s->dcaltime2, s->dark_int_time2, nummeas, s->gainmode);
				stm = msec_time();
				if ((ev = munki_dark_measure(p, s->dark_data2, nummeas, &s->dark_int_time2,
				                                                   s->gainmode)) != MUNKI_OK) {
					m->mmode = mmode;           /* Restore actual mode */
					return ev;
				}
				a1logd(p->log,4,"Execution time of 2nd dark calib time %f sec = %d msec\n",s->inttime,msec_time() - stm);
		
				nummeas = munki_comp_nummeas(p, s->dcaltime3, s->dark_int_time3);
				a1logd(p->log,3,"Doing 3rd initial black calibration with dcaltime3 %f, dark_int_time3 %f, nummeas %d, gainmode %d\n", s->dcaltime3, s->dark_int_time3, nummeas, s->gainmode);
				nummeas = munki_comp_nummeas(p, s->dcaltime3, s->dark_int_time3);
				stm = msec_time();
				if ((ev = munki_dark_measure(p, s->dark_data3, nummeas, &s->dark_int_time3,
					                                                   s->gainmode)) != MUNKI_OK) {
					m->mmode = mmode;           /* Restore actual mode */
					return ev;
				}
				a1logd(p->log,4,"Execution time of 3rd dark calib time %f sec = %d msec\n",s->inttime,msec_time() - stm);
		
			}
			s->dark_valid = 1;
			s->want_dcalib = 0;
			s->ddate = cdate;
			s->dark_int_time = s->inttime;
			s->dark_gain_mode = s->gainmode;
			*calt &= ~(inst_calt_ref_dark
		             | inst_calt_em_dark
		             | inst_calt_trans_dark);

			/* Save the calib to all similar modes */
			for (i = 0; i < mk_no_modes; i++) {
				munki_state *ss = &m->ms[i];
				if (ss == s || ss->ddate == cdate)
					continue;
				if ( (s->reflective
				  || (ss->emiss && !ss->adaptive && !ss->scan)
				  || (ss->trans && !ss->adaptive))
				 && ss->dark_int_time == s->dark_int_time
				 && ss->dark_gain_mode == s->dark_gain_mode) {

					ss->dark_valid = s->dark_valid;
					ss->want_dcalib = s->want_dcalib;
					ss->ddate = s->ddate;
					ss->dark_int_time = s->dark_int_time;
					ss->dark_gain_mode = s->dark_gain_mode;
					for (k = -1; k < m->nraw; k++)
						ss->dark_data[k] = s->dark_data[k];
					/* If this is a mode with dark_data2/3, tranfer it too */
					if (usesdct23 && ss->emiss && !ss->adaptive && !ss->scan) {
						ss->dark_int_time2 = s->dark_int_time2;
#ifndef NEVER	// ~~99
if (ss->dark_int_time2 != s->dark_int_time2
 || ss->dark_int_time3 != s->dark_int_time3)
	a1logd(p->log,1,"copying cal to mode with different cal/gain mode: %d -> %d\n",s->mode, ss->mode);
#endif
						ss->dark_int_time3 = s->dark_int_time2;
						for (k = -1; k < m->nraw; k++) {
							ss->dark_data2[k] = s->dark_data2[k];
							ss->dark_data3[k] = s->dark_data3[k];
						}
					}
				}
			}
		}
	
		/* Emissive scan black calibration: */
		/* Emsissive scan (flash) uses the fastest possible scan rate (??) */
		if ((*calt & (inst_calt_em_dark | inst_calt_ap_flag))
		 && (*calc & inst_calc_cond_mask) == inst_calc_man_cal_smode
		 && (s->emiss && !s->adaptive && s->scan)) {
			int stm;
	
			nummeas = munki_comp_nummeas(p, s->dcaltime, s->inttime);
	
			a1logd(p->log,3,"\nDoing emissive (flash) black calibration with dcaltime %f, int_time %f, nummeas %d, gainmode %d\n", s->dcaltime, s->inttime, nummeas, s->gainmode);
			stm = msec_time();
			if ((ev = munki_dark_measure(p, s->dark_data, nummeas, &s->inttime, s->gainmode))
		                                                                         != MUNKI_OK) {
				m->mmode = mmode;           /* Restore actual mode */
				return ev;
			}
			a1logd(p->log,4,"Execution time of dark calib time %f sec = %d msec\n",s->inttime,msec_time() - stm);
	
			s->dark_valid = 1;
			s->want_dcalib = 0;
			s->ddate = cdate;
			s->dark_int_time = s->inttime;
			s->dark_gain_mode = s->gainmode;
			*calt &= ~inst_calt_em_dark;
	
			/* Save the calib to all similar modes */
			for (i = 0; i < mk_no_modes; i++) {
				munki_state *ss = &m->ms[i];
				if (ss == s || ss->ddate == cdate)
					continue;
				if ((ss->emiss && !ss->adaptive && ss->scan)
				 && ss->dark_int_time == s->dark_int_time
				 && ss->dark_gain_mode == s->dark_gain_mode) {
					ss->dark_valid = s->dark_valid;
					ss->want_dcalib = s->want_dcalib;
					ss->ddate = s->ddate;
					ss->dark_int_time = s->dark_int_time;
					ss->dark_gain_mode = s->dark_gain_mode;
					for (k = -1; k < m->nraw; k++) {
						ss->dark_data[k] = s->dark_data[k];
					}
				}
			}
		}
	
		/* Adaptive black calibration: */
		/* Emmissive adaptive and transmissive black reference. */
		/* in non-scan mode, where the integration time and gain may vary. */
		if ((*calt & (inst_calt_ref_dark
		            | inst_calt_em_dark
		            | inst_calt_trans_dark | inst_calt_ap_flag))
		 && (*calc & inst_calc_cond_mask) == inst_calc_man_cal_smode
		 && ((s->emiss && s->adaptive && !s->scan)
		  || (s->trans && s->adaptive && !s->scan))) {
			/* Adaptive where we can't measure the black reference on the fly, */
			/* so bracket it and interpolate. */
			/* The black reference is probably temperature dependent, but */
			/* there's not much we can do about this. */
	
			s->idark_int_time[0] = m->min_int_time;
			nummeas = munki_comp_nummeas(p, s->dcaltime, s->idark_int_time[0]);
			a1logd(p->log,3,"\nDoing adaptive interpolated black calibration, dcaltime %f, idark_int_time[0] %f, nummeas %d, gainmode %d\n", s->dcaltime, s->idark_int_time[0], nummeas, 0);
			if ((ev = munki_dark_measure(p, s->idark_data[0], nummeas, &s->idark_int_time[0], 0))
			                                                                          != MUNKI_OK) {
				m->mmode = mmode;           /* Restore actual mode */
				return ev;
			}
		
			nummeas = munki_comp_nummeas(p, s->dcaltime, s->idark_int_time[1]);
			a1logd(p->log,3,"Doing adaptive interpolated black calibration, dcaltime %f, idark_int_time[1] %f, nummeas %d, gainmode %d\n", s->dcaltime, s->idark_int_time[1], nummeas, 0);
			if ((ev = munki_dark_measure(p, s->idark_data[1], nummeas, &s->idark_int_time[1], 0))
			                                                                          != MUNKI_OK) {
				m->mmode = mmode;           /* Restore actual mode */
				return ev;
			}
		
			if (s->auto_gain) {			/* If high gain is permitted */
				s->idark_int_time[2] = m->min_int_time;	/* 0.01 */
				nummeas = munki_comp_nummeas(p, s->dcaltime, s->idark_int_time[2]);
				a1logd(p->log,3,"Doing adaptive interpolated black calibration, dcaltime %f, idark_int_time[2] %f, nummeas %d, gainmode %d\n", s->dcaltime, s->idark_int_time[2], nummeas, 1);
				if ((ev = munki_dark_measure(p, s->idark_data[2], nummeas, &s->idark_int_time[2], 1))
				                                                                          != MUNKI_OK) {
					m->mmode = mmode;           /* Restore actual mode */
					return ev;
				}
			
				s->idark_int_time[3] = ADARKINT_MAX; /* 2.0 */
				a1logd(p->log,3,"Doing adaptive interpolated black calibration, dcaltime %f, idark_int_time[3] %f, nummeas %d, gainmode %d\n", s->dcaltime, s->idark_int_time[3], nummeas, 1);
				nummeas = munki_comp_nummeas(p, s->dcaltime, s->idark_int_time[3]);
				if ((ev = munki_dark_measure(p, s->idark_data[3], nummeas, &s->idark_int_time[3], 1))
				                                                                          != MUNKI_OK) {
					m->mmode = mmode;           /* Restore actual mode */
					return ev;
				}
			}
		
			munki_prepare_idark(p);
	
			s->idark_valid = 1;
			s->iddate = cdate;
		
			if ((ev = munki_interp_dark(p, s->dark_data, s->inttime, s->gainmode)) != MUNKI_OK) {
				m->mmode = mmode;           /* Restore actual mode */
				return ev;
			}
			s->dark_valid = 1;
			s->want_dcalib = 0;
			s->ddate = s->iddate;
			s->dark_int_time = s->inttime;
			s->dark_gain_mode = s->gainmode;
			*calt &= ~(inst_calt_ref_dark
		            | inst_calt_em_dark
		            | inst_calt_trans_dark);
	
			/* Save the calib to all similar modes */
			/* We're assuming they have the same int times */
			a1logd(p->log,3,"Saving adaptive black calib to similar modes\n");
			for (i = 0; i < mk_no_modes; i++) {
				munki_state *ss = &m->ms[i];
				if (ss == s || ss->iddate == cdate)
					continue;
				if ((ss->emiss || ss->trans) && ss->adaptive && !ss->scan) {
					ss->idark_valid = s->idark_valid;
					ss->want_dcalib = s->want_dcalib;
					ss->iddate = s->iddate;
					ss->dark_int_time = s->dark_int_time;
					ss->dark_gain_mode = s->dark_gain_mode;
#ifndef NEVER	// ~~99
if (ss->dark_int_time != s->dark_int_time
 || ss->dark_gain_mode != s->dark_gain_mode)
	a1logd(p->log,1,"copying cal to mode with different cal/gain mode: %d -> %d\n",s->mode, ss->mode);
#endif
					for (j = 0; j < (s->auto_gain ? 4 : 2); j++) {
						ss->idark_int_time[j] = s->idark_int_time[j];
#ifndef NEVER	// ~~99
if (ss->idark_int_time[j] != s->idark_int_time[j])
	a1logd(p->log,1,"copying cal to mode with different cal/gain mode: %d -> %d\n",s->mode, ss->mode);
#endif
						for (k = -1; k < m->nraw; k++)
							ss->idark_data[j][k] = s->idark_data[j][k];
					}
				}
			}
	
			a1logd(p->log,3,"Done adaptive interpolated black calibration\n");
	
			/* Test accuracy of dark level interpolation */
#ifdef TEST_DARK_INTERP
			{
				double tinttime;
				double ref[NSEN_MAX], interp[NSEN_MAX];
				
	//			fprintf(stderr,"Normal gain offsets, base:\n");
	//			plot_raw(s->idark_data[0]);
	//			fprintf(stderr,"Normal gain offsets, multiplier:\n");
	//			plot_raw(s->idark_data[1]);
				
#ifdef DUMP_DARKM
				extern int ddumpdarkm;
				ddumpdarkm = 1;
#endif
				for (tinttime = m->min_int_time; ; tinttime *= 2.0) {
					if (tinttime >= m->max_int_time)
						tinttime = m->max_int_time;
	
					nummeas = munki_comp_nummeas(p, s->dcaltime, tinttime);
					if ((ev = munki_dark_measure(p, ref, nummeas, &tinttime, 0)) != MUNKI_OK) {
						m->mmode = mmode;           /* Restore actual mode */
						return ev;
					}
					munki_interp_dark(p, interp, tinttime, 0);
#ifdef DEBUG
					fprintf(stderr,"Normal gain, int time %f:\n",tinttime);
					plot_raw2(ref, interp);
#endif
					if ((tinttime * 1.1) > m->max_int_time)
						break;
				}
#ifdef DUMP_DARKM
				ddumpdarkm = 0;
#endif
	
				if (s->auto_gain) {
//					fprintf(stderr,"High gain offsets, base:\n");
//					plot_raw(s->idark_data[2]);
//					fprintf(stderr,"High gain offsets, multiplier:\n");
//					plot_raw(s->idark_data[3]);
				
					for (tinttime = m->min_int_time; ; tinttime *= 2.0) {
						if (tinttime >= m->max_int_time)
							tinttime = m->max_int_time;
		
						nummeas = munki_comp_nummeas(p, s->dcaltime, tinttime);
						if ((ev = munki_dark_measure(p, ref, nummeas, &tinttime, 1)) != MUNKI_OK) {
							m->mmode = mmode;           /* Restore actual mode */
							return ev;
						}
						munki_interp_dark(p, interp, tinttime, 1);
#ifdef DEBUG
						printf("High gain, int time %f:\n",tinttime);
						plot_raw2(ref, interp);
#endif
						if ((tinttime * 1.1) > m->max_int_time)
							break;
					}
			}
			}
#endif	/* TEST_DARK_INTERP */
	
		}
	
		/* Deal with an emissive/transmisive adaptive black reference */
		/* when in scan mode. */
		if ((*calt & (inst_calt_ref_dark
		            | inst_calt_em_dark
		            | inst_calt_trans_dark | inst_calt_ap_flag))
		 && (*calc & inst_calc_cond_mask) == inst_calc_man_cal_smode
		 && ((s->emiss && s->adaptive && s->scan)
		  || (s->trans && s->adaptive && s->scan))) {
			int j;
			/* We know scan is locked to the minimum integration time, */
			/* so we can measure the dark data at that integration time, */
			/* but we don't know what gain mode will be used, so measure both, */
			/* and choose the appropriate one on the fly. */
		
			s->idark_int_time[0] = s->inttime;
			nummeas = munki_comp_nummeas(p, s->dcaltime, s->idark_int_time[0]);
			a1logd(p->log,3,"\nDoing adaptive scan black calibration, dcaltime %f, idark_int_time[0] %f, nummeas %d, gainmode %d\n", s->dcaltime, s->idark_int_time[0], nummeas, s->gainmode);
			if ((ev = munki_dark_measure(p, s->idark_data[0], nummeas, &s->idark_int_time[0], 0))
			                                                                          != MUNKI_OK) {
				m->mmode = mmode;           /* Restore actual mode */
				return ev;
			}
		
			if (s->auto_gain) {
				s->idark_int_time[2] = s->inttime;
				nummeas = munki_comp_nummeas(p, s->dcaltime, s->idark_int_time[2]);
				a1logd(p->log,3,"Doing adaptive scan black calibration, dcaltime %f, idark_int_time[2] %f, nummeas %d, gainmode %d\n", s->dcaltime, s->idark_int_time[2], nummeas, s->gainmode);
				if ((ev = munki_dark_measure(p, s->idark_data[2], nummeas, &s->idark_int_time[2], 1))
				                                                                          != MUNKI_OK) {
					m->mmode = mmode;           /* Restore actual mode */
					return ev;
				}
			}
		
			s->idark_valid = 1;
			s->iddate = cdate;
	
			if (s->auto_gain && s->gainmode) {
				for (j = -1; j < m->nraw; j++)
					s->dark_data[j] = s->idark_data[2][j];
			} else {
				for (j = -1; j < m->nraw; j++)
					s->dark_data[j] = s->idark_data[0][j];
			}
			s->dark_valid = 1;
			s->want_dcalib = 0;
			s->ddate = s->iddate;
			s->dark_int_time = s->inttime;
			s->dark_gain_mode = s->gainmode;
			*calt &= ~(inst_calt_ref_dark
		            | inst_calt_em_dark
		            | inst_calt_trans_dark);
	
			a1logd(p->log,3,"Done adaptive scan black calibration\n");
	
			/* Save the calib to all similar modes */
			/* We're assuming they have the same int times */
			a1logd(p->log,3,"Saving adaptive scan black calib to similar modes\n");
			for (i = 0; i < mk_no_modes; i++) {
				munki_state *ss = &m->ms[i];
				if (ss == s || s->iddate == cdate)
					continue;
				if ((ss->emiss || ss->trans) && ss->adaptive && s->scan) {
					ss->idark_valid = s->idark_valid;
					ss->want_dcalib = s->want_dcalib;
					ss->iddate = s->iddate;
					ss->dark_int_time = s->dark_int_time;
					ss->dark_gain_mode = s->dark_gain_mode;

					for (j = 0; j < (s->auto_gain ? 4 : 2); j += 2) {
						ss->idark_int_time[j] = s->idark_int_time[j];
						for (k = -1; k < m->nraw; k++)
							ss->idark_data[j][k] = s->idark_data[j][k];
					}
				}
			}
		}
	
		/* Now deal with white calibrations */
	
		/* If we are doing a reflective white reference calibrate */
		/* or a we are doing a tranmisive white reference calibrate */
		if ((*calt & (inst_calt_ref_white
		            | inst_calt_trans_vwhite | inst_calt_ap_flag))
		 && (((*calc & inst_calc_cond_mask) == inst_calc_man_cal_smode && s->reflective) 
		  || ((*calc & inst_calc_cond_mask) == inst_calc_man_trans_white && s->trans))) {
//		 && s->cfdate < cdate)
			double dead_time = 0.0;		/* Dead integration time */
			double scale;
			int i;
			double ulimit = m->optsval / m->minsval;	/* Upper scale needed limit */
			double fulimit = sqrt(ulimit);				/* Fast exit limit */
			double llimit = m->optsval / m->maxsval;	/* Lower scale needed limit */
			double fllimit = sqrt(llimit);				/* Fast exit limit */
	
			a1logd(p->log,3,"\nDoing initial white calibration with current inttime %f, gainmode %d\n",
			                                                   s->inttime, s->gainmode);
			a1logd(p->log,3,"ulimit %f, llimit %f\n",ulimit,llimit);
			a1logd(p->log,3,"fulimit %f, fllimit %f\n",fulimit,fllimit);
			if (s->reflective) {
				dead_time = RDEAD_TIME;		/* Fudge value that makes int time calcs work */
				/* Heat up the LED to put in in a nominal state for int time adjustment */
				munki_heatLED(p, m->ledpreheattime);
			}
			
			/* Until we're done */
			for (i = 0; i < 6; i++) {
	
				a1logd(p->log,3,"Doing a white calibration with trial int_time %f, gainmode %d\n",
				                                                 s->inttime,s->gainmode);
	
				if (s->trans && s->adaptive) {
					/* compute interpolated dark refence for chosen inttime & gainmode */
					a1logd(p->log,3,"Interpolate dark calibration reference\n");
					if ((ev = munki_interp_dark(p, s->dark_data, s->inttime, s->gainmode))
					                                                             != MUNKI_OK) {
						m->mmode = mmode;           /* Restore actual mode */
						return ev;
					}
					s->dark_valid = 1;
					s->ddate = s->iddate;
					s->dark_int_time = s->inttime;
					s->dark_gain_mode = s->gainmode;
				}
				nummeas = munki_comp_nummeas(p, s->wcaltime, s->inttime);
				ev = munki_whitemeasure(p, s->white_data, &scale, nummeas, &s->inttime, s->gainmode,
				                                                                      s->targoscale);
				a1logd(p->log,3,"Needed scale is %f\n",scale);
	
				if (ev == MUNKI_RD_SENSORSATURATED) {
					scale = 0.0;			/* Signal it this way */
					ev = MUNKI_OK;
				}
				if (ev != MUNKI_OK) {
					m->mmode = mmode;           /* Restore actual mode */
					return ev;
				}
	
				if (scale >= fllimit && scale <= fulimit) {
					a1logd(p->log,3,"Close enough for early exit\n");
					break;			/* OK, we can stop straight away */
				}
		
				if (scale == 0.0) {		/* If sensor was saturated */
					s->inttime = m->min_int_time;
					s->gainmode = 0;
					s->dark_valid = 0;
				} else {
					double ninttime;
	
					/* Compute a new integration time and gain mode */
					/* in order to optimise the sensor values. Error if can't get */
					/* scale we want. */
					if ((ev = munki_optimise_sensor(p, &ninttime, &s->gainmode, s->inttime,
					    s->gainmode, s->trans, 0, &s->targoscale, scale, dead_time)) != MUNKI_OK) {
						m->mmode = mmode;           /* Restore actual mode */
						return ev;
					}
					s->inttime = ninttime;
					a1logd(p->log,3,"New inttime = %f\n",s->inttime);
				}
			}
			if (i >= 6) {
				if (scale == 0.0) {		/* If sensor was saturated */
					a1logd(p->log,1, "White calibration failed - sensor is saturated\n");
					m->mmode = mmode;           /* Restore actual mode */
					return MUNKI_RD_SENSORSATURATED;
				}
				if (scale > ulimit || scale < llimit) {
					a1logd(p->log,1,"White calibration failed - didn't converge (%f %f %f)\n",llimit,scale,ulimit);
					m->mmode = mmode;           /* Restore actual mode */
					return MUNKI_RD_REFWHITENOCONV; 
				}
			}
	
			/* We've settled on the inttime and gain mode to get a good white reference. */
			if (s->reflective) {	/* We read the write reference - check it */
	
				/* Let the LED cool down */
				a1logd(p->log,3,"Waiting %f secs for LED to cool\n",m->ledwaittime);
				msec_sleep((int)(m->ledwaittime * 1000.0 + 0.5));
	
				/* Re-calibrate the black with the given integration time */
				nummeas = munki_comp_nummeas(p, s->dcaltime, s->inttime);
	
				a1logd(p->log,3,"Doing another reflective black calibration with dcaltime %f, int_time %f, nummeas %d, gainmode %d\n", s->dcaltime, s->inttime, nummeas, s->gainmode);
				if ((ev = munki_dark_measure(p, s->dark_data, nummeas, &s->inttime, s->gainmode))
			                                                                         != MUNKI_OK) {
					m->mmode = mmode;           /* Restore actual mode */
					return ev;
				}
	
				/* Take a reflective white reference measurement, */
				/* subtracts black and decompose into base + LED temperature components, */
				/* and compute reftemp white reference. */
				nummeas = munki_comp_nummeas(p, m->calscantime, s->inttime);
				if ((ev = munki_ledtemp_whitemeasure(p, s->white_data, s->iwhite_data, &s->reftemp,
				                                  nummeas, s->inttime, s->gainmode)) != MUNKI_OK) {
					m->mmode = mmode;           /* Restore actual mode */
					return ev;
				}
	
				/* Compute wavelength white readings from ref temp sensor reading */
				if ((ev = munki_compute_wav_whitemeas(p, s->cal_factor1, s->cal_factor2,
				                                             s->white_data)) != MUNKI_OK) {
					m->mmode = mmode;           /* Restore actual mode */
					return ev;
				}
	
				/* We don't seem to sanity check the white reference. Presumably */
				/* this is because a LED isn't going to burn out... */
	
				/* Compute a calibration factor given the reading of the white reference. */
				munki_compute_white_cal(p, s->cal_factor1, m->white_ref1, s->cal_factor1,
				                           s->cal_factor2, m->white_ref2, s->cal_factor2);
	
			} else {
				/* Compute wavelength white readings from sensor */
				if ((ev = munki_compute_wav_whitemeas(p, s->cal_factor1, s->cal_factor2,
				                                             s->white_data)) != MUNKI_OK) {
					m->mmode = mmode;           /* Restore actual mode */
					return ev;
				}
	
				/* Compute a calibration factor given the reading of the white reference. */
				m->transwarn |= munki_compute_white_cal(p, s->cal_factor1, NULL, s->cal_factor1,
				                                       s->cal_factor2, NULL, s->cal_factor2);
			}
			s->cal_valid = 1;
			s->cfdate = cdate;
			s->want_calib = 0;
			*calt &= ~(inst_calt_ref_white
			         | inst_calt_trans_vwhite);
		}
	
		/* Deal with a display integration time selection */
		if ((*calt & (inst_calt_emis_int_time | inst_calt_ap_flag))
		 && (*calc & inst_calc_cond_mask) == inst_calc_emis_white
//		 && s->cfdate < cdate
		 && (s->emiss && !s->adaptive && !s->scan)) {
			double scale;
			double *data;
			double *tt, tv;
	
			data = dvectorz(-1, m->nraw-1);
	
			a1logd(p->log,3,"\nDoing display integration time calibration\n");
	
			/* Undo any previous swaps */
			if (s->dispswap == 1) {
				tv = s->inttime; s->inttime = s->dark_int_time2; s->dark_int_time2 = tv;
				tt = s->dark_data; s->dark_data = s->dark_data2; s->dark_data2 = tt;
			} else if (s->dispswap == 2) {
				tv = s->inttime; s->inttime = s->dark_int_time3; s->dark_int_time3 = tv;
				tt = s->dark_data; s->dark_data = s->dark_data3; s->dark_data3 = tt;
			}
			s->dispswap = 0;

			/* Simply measure the full display white, and if it's close to */
			/* saturation, switch to the alternate display integration time */
			nummeas = munki_comp_nummeas(p, s->wreadtime, s->inttime);
			ev = munki_whitemeasure(p, data , &scale, nummeas,
			           &s->inttime, s->gainmode, s->targoscale);
			/* Switch to the alternate if things are too bright */
			/* We do this simply by swapping the alternate values in. */
			if (ev == MUNKI_RD_SENSORSATURATED || scale < 1.0) {
				a1logd(p->log,3,"Switching to alternate display integration time %f seconds\n",s->dark_int_time2);
				tv = s->inttime; s->inttime = s->dark_int_time2; s->dark_int_time2 = tv;
				tt = s->dark_data; s->dark_data = s->dark_data2; s->dark_data2 = tt;
				s->dispswap = 1;
	
				/* Do another measurement of the full display white, and if it's close to */
				/* saturation, switch to the 3rd alternate display integration time */
				nummeas = munki_comp_nummeas(p, s->wreadtime, s->inttime);
				ev = munki_whitemeasure(p, data , &scale, nummeas,
				           &s->inttime, s->gainmode, s->targoscale);
				/* Switch to the 3rd alternate if things are too bright */
				/* We do this simply by swapping the alternate values in. */
				if (ev == MUNKI_RD_SENSORSATURATED || scale < 1.0) {
					a1logd(p->log,3,"Switching to 3rd alternate display integration time %f seconds\n",s->dark_int_time3);
					/* Undo previous swap */
					tv = s->inttime; s->inttime = s->dark_int_time2; s->dark_int_time2 = tv;
					tt = s->dark_data; s->dark_data = s->dark_data2; s->dark_data2 = tt;
					/* swap in 2nd alternate */
					tv = s->inttime; s->inttime = s->dark_int_time3; s->dark_int_time3 = tv;
					tt = s->dark_data; s->dark_data = s->dark_data3; s->dark_data3 = tt;
					s->dispswap = 2;
				}
			}
			free_dvector(data, -1, m->nraw-1);
			if (ev != MUNKI_OK) {
				m->mmode = mmode;           /* Restore actual mode */
				return ev;
			}
			s->done_dintsel = 1;
			s->diseldate = cdate;
			*calt &= ~inst_calt_emis_int_time;
	
			a1logd(p->log,3,"Done display integration time selection\n");
		}

	}	/* Look at next mode */
	m->mmode = mmode;           /* Restore actual mode */

	/* Make sure there's the right condition for the calibration */
	if (*calt & (inst_calt_ref_dark | inst_calt_ref_white)) {	/* Reflective calib */
		if ((*calc & inst_calc_cond_mask) != inst_calc_man_cal_smode) {
			*calc = inst_calc_man_cal_smode;
			return MUNKI_CAL_SETUP;
		}
	} else if (*calt & inst_calt_em_dark) {		/* Emissive Dark calib */
		*idtype = inst_calc_id_none;
		id[0] = '\000';
		if ((*calc & inst_calc_cond_mask) != inst_calc_man_cal_smode) {
			*calc = inst_calc_man_cal_smode;
			return MUNKI_CAL_SETUP;
		}
	} else if (*calt & inst_calt_trans_dark) {	/* Transmissive dark */
		*idtype = inst_calc_id_none;
		id[0] = '\000';
		if ((*calc & inst_calc_cond_mask) != inst_calc_man_cal_smode) {
			*calc = inst_calc_man_cal_smode;
			return MUNKI_CAL_SETUP;
		}
	} else if (*calt & inst_calt_trans_vwhite) {	/* Transmissive white for emulated trans. */
		*idtype = inst_calc_id_none;
		id[0] = '\000';
		if ((*calc & inst_calc_cond_mask) != inst_calc_man_trans_white) {
			*calc = inst_calc_man_trans_white;
			return MUNKI_CAL_SETUP;
		}
	} else if (*calt & inst_calt_emis_int_time) {
		*idtype = inst_calc_id_none;
		id[0] = '\000';
		if ((*calc & inst_calc_cond_mask) != inst_calc_emis_white) {
			*calc = inst_calc_emis_white;
			return MUNKI_CAL_SETUP;
		}
	}

	/* Go around again if we've still got calibrations to do */
	if (*calt & inst_calt_all_mask) {
		return MUNKI_CAL_SETUP;
	}

	/* We must be done */

#ifdef ENABLE_NONVCAL
	/* Save the calibration to a file */
	munki_save_calibration(p);
#endif
	
	if (m->transwarn) {
		*calc = inst_calc_message;
		if (m->transwarn & 2) {
			*idtype = inst_calc_id_trans_low;
			strcpy(id, "Warning: Transmission light source is too low for accuracy!");
		} else {
			*idtype = inst_calc_id_trans_wl;
			strcpy(id, "Warning: Transmission light source is low at some wavelengths!");
		}
		m->transwarn = 0;
		return MUNKI_OK;
	}

	a1logd(p->log,3,"Finished cal with dark_valid = %d, cal_valid = %d\n",cs->dark_valid, cs->cal_valid);

	return ev; 
}

/* Interpret an icoms error into a MUNKI error */
int icoms2munki_err(int se) {
	if (se != ICOM_OK)
		return MUNKI_COMS_FAIL;
	return MUNKI_OK;
}

/* - - - - - - - - - - - - - - - - */
/* Measure a display update delay. It is assumed that */
/* white_stamp(init) has been called, and then a */
/* white to black change has been made to the displayed color, */
/* and this will measure the time it took for the update to */
/* be noticed by the instrument, up to 2.0 seconds. */
/* (It is assumed that white_change() will be called at the time the patch */
/* changes color.) */
/* inst_misread will be returned on failure to find a transition to black. */
#define NDMXTIME 2.0		/* Maximum time to take */
#define NDSAMPS 500			/* Debug samples */

typedef struct {
	double sec;
	double rgb[3];
	double tot;
} i1rgbdsamp;

munki_code munki_imp_meas_delay(
munki *p,
int *pdispmsec,      /* Return display update delay in msec */
int *pinstmsec) {    /* Return instrument reaction time in msec */
	munki_code ev = MUNKI_OK;
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	int i, j, k, mm;
	double **multimeas;			/* Spectral measurements */
	int nummeas;
	double rgbw[3] = { 610.0, 520.0, 460.0 };
	double ucalf = 1.0;				/* usec_time calibration factor */
	double inttime;
	double rstart;
	i1rgbdsamp *samp;
	double stot, etot, del, thr;
	double stime, etime;
	int isdeb;
	int dispmsec, instmsec;

	if (pinstmsec != NULL)
		*pinstmsec = 0; 

	if ((rstart = usec_time()) < 0.0) {
		a1loge(p->log, inst_internal_error, "munki_imp_meas_delay: No high resolution timers\n");
		return inst_internal_error; 
	}

	/* Read the samples */
	inttime = m->min_int_time;
	nummeas = (int)(NDMXTIME/inttime + 0.5);
	multimeas = dmatrix(0, nummeas-1, -1, m->nwav-1);
	if ((samp = (i1rgbdsamp *)calloc(sizeof(i1rgbdsamp), nummeas)) == NULL) {
		a1logd(p->log, 1, "munki_meas_delay: malloc failed\n");
		return MUNKI_INT_MALLOC;
	}

	if ((ev = munki_read_patches_all(p, multimeas, nummeas, &inttime, 0)) != inst_ok) {
		free_dmatrix(multimeas, 0, nummeas-1, 0, m->nwav-1);
		free(samp);
		return ev;
	} 

	if (m->whitestamp < 0.0) {
		a1logd(p->log, 1, "munki_meas_delay: White transition wasn't timestamped\n");
		return inst_internal_error; 
	}

	/* Convert the samples to RGB */
	/* Add 10 msec fudge factor */
	for (i = 0; i < nummeas; i++) {
		samp[i].sec = i * inttime + (m->trigstamp - m->whitestamp)/1000000.0 + 0.01;
		samp[i].rgb[0] = samp[i].rgb[1] = samp[i].rgb[2] = 0.0;
		for (j = 0; j < m->nwav; j++) {
			double wl = XSPECT_WL(m->wl_short, m->wl_long, m->nwav, j);

//printf("~1 multimeas %d %d = %f\n",i, j, multimeas[i][j]);
			for (k = 0; k < 3; k++) {
				double tt = (double)(wl - rgbw[k]);
				tt = (50.0 - fabs(tt))/50.0;
				if (tt < 0.0)
					tt = 0.0;
				samp[i].rgb[k] += sqrt(tt) * multimeas[i][j];
			}
		}
		samp[i].tot = samp[i].rgb[0] + samp[i].rgb[1] + samp[i].rgb[2];
	}
	free_dmatrix(multimeas, 0, nummeas-1, 0, m->nwav-1);

	a1logd(p->log, 3, "munki_measure_refresh: Read %d samples for refresh calibration\n",nummeas);

	/* Over the first 100msec, locate the maximum value */
	stime = samp[0].sec;
	stot = -1e9;
	for (i = 0; i < nummeas; i++) {
		if (samp[i].tot > stot)
			stot = samp[i].tot;
		if ((samp[i].sec - stime) > 0.1)
			break;
	}

	/* Over the last 100msec, locate the maximum value */
	etime = samp[nummeas-1].sec;
	etot = -1e9;
	for (i = nummeas-1; i >= 0; i--) {
		if (samp[i].tot > etot)
			etot = samp[i].tot;
		if ((etime - samp[i].sec) > 0.1)
			break;
	}

	del = etot - stot;
	thr = stot + 0.30 * del;		/* 30% of transition threshold */

#ifdef PLOT_UPDELAY
	a1logd(p->log, 0, "munki_meas_delay: start tot %f end tot %f del %f, thr %f\n", stot, etot, del, thr);
#endif

#ifdef PLOT_UPDELAY
	/* Plot the raw sensor values */
	{
		double xx[NDSAMPS];
		double y1[NDSAMPS];
		double y2[NDSAMPS];
		double y3[NDSAMPS];
		double y4[NDSAMPS];

		for (i = 0; i < nummeas && i < NDSAMPS; i++) {
			xx[i] = samp[i].sec;
			y1[i] = samp[i].rgb[0];
			y2[i] = samp[i].rgb[1];
			y3[i] = samp[i].rgb[2];
			y4[i] = samp[i].tot;
//printf("%d: %f -> %f\n",i,samp[i].sec, samp[i].tot);
		}
		plot_msg("Display update delay measure sensor values and time (sec)\n");
		do_plot6(xx, y1, y2, y3, y4, NULL, NULL, nummeas);
	}
#endif

	/* Check that there has been a transition */
	if (del < 5.0) {
		free(samp);
		a1logd(p->log, 1, "munki_meas_delay: can't detect change from black to white\n");
		return MUNKI_RD_NOTRANS_FOUND; 
	}

	/* Working from the start, locate the time at which the level was above the threshold */
	for (i = 0; i < (nummeas-1); i++) {
		if (samp[i].tot > thr)
			break;
	}

	a1logd(p->log, 2, "munki_meas_delay: stopped at sample %d time %f\n",i,samp[i].sec);

	/* Compute overall delay and subtract patch change delay */
	dispmsec = (int)(samp[i].sec * 1000.0 + 0.5);
	instmsec = (int)((m->trigstamp - rstart)/1000.0 + 0.5);

#ifdef PLOT_UPDELAY
	a1logd(p->log, 0, "munki_meas_delay: disp %d, inst %d msec\n",dispmsec,instmsec);
#else
	a1logd(p->log, 2, "munki_meas_delay: disp %d, inst %d msec\n",dispmsec,instmsec);
#endif

	if (dispmsec < 0) 		/* This can happen if the patch generator delays it's return */
		dispmsec = 0;

	if (pdispmsec != NULL)
		*pdispmsec = dispmsec;

	if (pinstmsec != NULL)
		*pinstmsec = instmsec;

#ifdef PLOT_UPDELAY
	a1logd(p->log, 0, "munki_meas_delay: returning %d & %d msec\n",dispmsec,instmsec);
#else
	a1logd(p->log, 2, "munki_meas_delay: returning %d & %d msec\n",dispmsec,instmsec);
#endif
	free(samp);

	return MUNKI_OK;
}
#undef NDSAMPS
#undef NDMXTIME

/* Timestamp the white patch change during meas_delay() */
inst_code munki_imp_white_change(munki *p, int init) {
	munkiimp *m = (munkiimp *)p->m;

	if (init)
		m->whitestamp = -1.0;
	else {
		if ((m->whitestamp = usec_time()) < 0.0) {
			a1loge(p->log, inst_internal_error, "munki_imp_wite_change: No high resolution timers\n");
			return inst_internal_error; 
		}
	}

	return inst_ok;
}

/* - - - - - - - - - - - - - - - - */
/* Measure a patch or strip or flash in the current mode. */
/* To try and speed up the reaction time between */
/* triggering a scan measurement and being able to */
/* start moving the instrument, we pre-allocate */
/* all the buffers and arrays, and pospone processing */
/* until after the scan is complete. */
munki_code munki_imp_measure(
	munki *p,
	ipatch *vals,		/* Pointer to array of instrument patch value */
	int nvals,			/* Number of values */	
	instClamping clamp	/* Clamp XYZ/Lab to be +ve */
) {
	munki_code ev = MUNKI_OK;
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	unsigned char *buf = NULL;	/* Raw USB reading buffer for reflection dark cal */
	unsigned int bsize;
	unsigned char *mbuf = NULL;	/* Raw USB reading buffer for measurement */
	unsigned int mbsize;
	int nummeas = 0, maxnummeas = 0;
	int nmeasuered = 0;			/* Number actually measured */
	double invsampt = 0.0;		/* Invalid sample time */			
	int ninvmeas = 0;			/* Number of invalid measurements */
	double **specrd = NULL;		/* Cooked spectral patch values */
	double duration = 0.0;		/* Possible flash duration value */
	mk_spos spos;
	int user_trig = 0;

	a1logd(p->log,2,"munki_imp_measure called\n");
	a1logd(p->log,3,"Taking %d measurments in %s%s%s%s%s mode called\n", nvals,
		        s->emiss ? "Emission" : s->trans ? "Trans" : "Refl", 
		        s->emiss && s->ambient ? " Ambient" : "",
		        s->scan ? " Scan" : "",
		        s->flash ? " Flash" : "",
		        s->adaptive ? " Adaptive" : "");


	if ((s->emiss && s->adaptive && !s->idark_valid)
	 || ((!s->emiss || !s->adaptive) && !s->dark_valid)
	 || !s->cal_valid) {
		a1logd(p->log,3,"emis %d, adaptive %d, idark_valid %d\n",s->emiss,s->adaptive,s->idark_valid);
		a1logd(p->log,3,"dark_valid %d, cal_valid %d\n",s->dark_valid,s->cal_valid);
		a1logd(p->log,3,"munki_imp_measure need calibration\n");
		return MUNKI_RD_NEEDS_CAL;
	}
		
	if (nvals <= 0
	 || (!s->scan && nvals > 1)) {
		a1logd(p->log,3,"munki_imp_measure wrong number of patches\n");
		return MUNKI_INT_WRONGPATCHES;
	}

	if (s->reflective) {
		/* Number of invalid samples to allow for LED warmup */
		invsampt = m->refinvalidsampt;
		ninvmeas = munki_comp_ru_nummeas(p, invsampt, s->inttime);
	}

	/* Notional number of measurements, befor adaptive and not counting scan */
	nummeas = munki_comp_nummeas(p, s->wreadtime, s->inttime);

	/* Allocate buffer for dark measurement */
	if (s->reflective) {
		bsize = m->nsen * 2 * nummeas;
		if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
			a1logd(p->log,1,"munki_imp_measure malloc %d bytes failed (5)\n",bsize);
			return MUNKI_INT_MALLOC;
		}
	}

	/* Allocate buffer for measurement */
	maxnummeas = munki_comp_nummeas(p, s->maxscantime, s->inttime);
	if (maxnummeas < (ninvmeas + nummeas))
		maxnummeas = (ninvmeas + nummeas);
	mbsize = m->nsen * 2 * maxnummeas;
	if ((mbuf = (unsigned char *)malloc(sizeof(unsigned char) * mbsize)) == NULL) {
		if (buf != NULL)
			free(buf);
		a1logd(p->log,1,"munki_imp_measure malloc %d bytes failed (6)\n",mbsize);
		return MUNKI_INT_MALLOC;
	}
	specrd = dmatrix(0, nvals-1, 0, m->nwav-1);

	if (m->trig == inst_opt_trig_user_switch) {
		m->hide_switch = 1;						/* Supress switch events */

#ifdef USE_THREAD
		{
			int currcount = m->switch_count;		/* Variable set by thread */
			while (currcount == m->switch_count) {
				inst_code rc;
				int cerr;
	
				/* Don't trigger on user key if scan, only trigger */
				/* on instrument switch */
				if (p->uicallback != NULL
				 && (rc = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
					if (rc == inst_user_abort) {
						ev = MUNKI_USER_ABORT;
						break;						/* Abort */
					}
					if (!s->scan && rc == inst_user_trig) {
						ev = MUNKI_USER_TRIG;
						user_trig = 1;
						break;						/* Trigger */
					}
				}
				msec_sleep(100);
			}
		}
#else
		/* Throw one away in case the switch was pressed prematurely */
		munki_waitfor_switch_th(p, NULL, NULL, 0.01);

		for (;;) {
			mk_eve ecode;
			int cerr;

			if ((ev = munki_waitfor_switch_th(p, &ecode, NULL, 0.1)) != MUNKI_OK
			 && ev != MUNKI_INT_BUTTONTIMEOUT)
				break;			/* Error */

			if (ev == MUNKI_OK && ecode == mk_eve_switch_press)
				break;			/* switch triggered */

			/* Don't trigger on user key if scan, only trigger */
			/* on instrument switch */
			if (p->uicallback != NULL
			  && (rc = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
				if (rc == inst_user_abort) {
					ev = MUNKI_USER_ABORT;
					break;						/* Abort */
				}
				if (!s->scan && rc == inst_user_trig) {
					ev = MUNKI_USER_TRIG;
					user_trig = 1;
					break;						/* Trigger */
				}
			}
		}
#endif
		a1logd(p->log,3,"############# triggered ##############\n");
		if (p->uicallback)	/* Notify of trigger */
			p->uicallback(p->uic_cntx, inst_triggered); 

		m->hide_switch = 0;						/* Enable switch events again */

	} else if (m->trig == inst_opt_trig_user) {

		if (p->uicallback == NULL) {
			a1logd(p->log, 1, "hcfr: inst_opt_trig_user but no uicallback function set!\n");
			ev = MUNKI_UNSUPPORTED;

		} else {

			for (;;) {
				inst_code rc;
				if ((rc = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
					if (rc == inst_user_abort) {
						ev = MUNKI_USER_ABORT;	/* Abort */
						break;
					}
					if (rc == inst_user_trig) {
						ev = MUNKI_USER_TRIG;
						user_trig = 1;
						break;						/* Trigger */
					}
				}
				msec_sleep(200);
			}
		}
		a1logd(p->log,3,"############# triggered ##############\n");
		if (p->uicallback)	/* Notify of trigger */
			p->uicallback(p->uic_cntx, inst_triggered); 

	/* Progromatic Trigger */
	} else {
		/* Check for abort */
		if (p->uicallback != NULL
		 && (ev = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_abort)
			ev = MUNKI_USER_ABORT;	/* Abort */
	}

	if (ev != MUNKI_OK && ev != MUNKI_USER_TRIG) {
		free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
		free(mbuf);
		if (buf != NULL)
			free(buf);
		a1logd(p->log,3,"munki_imp_measure user aborted, terminated, command, or failure\n");
		return ev;		/* User abort, term, command or failure */
	}

	/* Get current sensor position */
	if ((ev = munki_getstatus(p, &spos, NULL)) != MUNKI_OK) {
		free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
		free(mbuf);
		if (buf != NULL)
			free(buf);
		a1logd(p->log,3,"munki_imp_measure getstatus failed\n");
		return ev;
	}

	/* Check the current sensor position */
	if (!m->nosposcheck) {
		if (s->emiss) {
			if (s->ambient) {
				if (spos != mk_spos_amb)
					ev = MUNKI_SPOS_AMB;
			} else if (s->projector) {
				if (spos != mk_spos_proj)
					ev = MUNKI_SPOS_PROJ;
			} else {	/* Display */
				if (spos != mk_spos_surf)
					ev = MUNKI_SPOS_SURF;
			}
		} else {	/* Reflective or transmissive */
			if (spos != mk_spos_surf)
				ev = MUNKI_SPOS_SURF;
		}
		if (ev != MUNKI_OK) {
			free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
			free(mbuf);
			if (buf != NULL)
				free(buf);
			a1logd(p->log,3,"munki_imp_measure: Sensor in wrong position\n");
			return ev;
		}
	}

	/* Emissive adaptive, non-scan */
	if (s->emiss && !s->scan && s->adaptive) {
		int saturated = 0;
		double optscale = 1.0;
		s->inttime = 0.25;
		s->gainmode = 0;
		s->dark_valid = 0;

		a1logd(p->log,3,"Trial measure emission with inttime %f, gainmode %d\n",s->inttime,s->gainmode);

		/* Take a trial measurement reading using the current mode. */
		/* Used to determine if sensor is saturated, or not optimal */
		nummeas = munki_comp_nummeas(p, s->wreadtime, s->inttime);
		if ((ev = munki_trialmeasure(p, &saturated, &optscale, nummeas, &s->inttime, s->gainmode,
		                                                            s->targoscale)) != MUNKI_OK) {
			free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
			free(mbuf);
			a1logd(p->log,3,"munki_imp_measure trial measure failed\n");
			return ev;  
		}

		if (saturated) {
			s->inttime = m->min_int_time;

			a1logd(p->log,3,"2nd trial measure emission with inttime %f, gainmode %d\n",
			                                         s->inttime,s->gainmode);
			/* Take a trial measurement reading using the current mode. */
			/* Used to determine if sensor is saturated, or not optimal */
			nummeas = munki_comp_nummeas(p, s->wreadtime, s->inttime);
			if ((ev = munki_trialmeasure(p, &saturated, &optscale, nummeas, &s->inttime, 
			                                          s->gainmode, s->targoscale)) != MUNKI_OK) {
				free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
				free(mbuf);
				a1logd(p->log,3,"munki_imp_measure trial measure failed\n");
				return ev;  
			}
		}

		a1logd(p->log,3,"Compute optimal integration time\n");
		/* For adaptive mode, compute a new integration time and gain mode */
		/* in order to optimise the sensor values. */
		if ((ev = munki_optimise_sensor(p, &s->inttime, &s->gainmode, 
		         s->inttime, s->gainmode, 1, 1, &s->targoscale, optscale, 0.0)) != MUNKI_OK) {
			free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
			free(mbuf);
			a1logd(p->log,3,"munki_imp_measure optimise sensor failed\n");
			return ev;
		}
		a1logd(p->log,3,"Computed optimal emiss inttime %f and gainmode %d\n",s->inttime,s->gainmode);

		a1logd(p->log,3,"Interpolate dark calibration reference\n");
		if ((ev = munki_interp_dark(p, s->dark_data, s->inttime, s->gainmode)) != MUNKI_OK) {
			free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
			free(mbuf);
			a1logd(p->log,3,"munki_imp_measure interplate dark ref failed\n");
			return ev;
		}
		s->dark_valid = 1;
		s->dark_int_time = s->inttime;
		s->dark_gain_mode = s->gainmode;

		/* Recompute number of measurements and realloc measurement buffer */
		free(mbuf);
		nummeas = munki_comp_nummeas(p, s->wreadtime, s->inttime);
		maxnummeas = munki_comp_nummeas(p, s->maxscantime, s->inttime);
		if (maxnummeas < nummeas)
			maxnummeas = nummeas;
		mbsize = m->nsen * 2 * maxnummeas;
		if ((mbuf = (unsigned char *)malloc(sizeof(unsigned char) * mbsize)) == NULL) {
			free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
			a1logd(p->log,1,"munki_imp_measure malloc %d bytes failed (7)\n",mbsize);
			return MUNKI_INT_MALLOC;
		}

	} else if (s->reflective) {

		DISDPLOT

		a1logd(p->log,3,"Doing on the fly black calibration_1 with nummeas %d int_time %f, gainmode %d\n",
		                                                   nummeas, s->inttime, s->gainmode);

		if ((ev = munki_dark_measure_1(p, nummeas, &s->inttime, s->gainmode, buf, bsize))
		                                                                           != MUNKI_OK) {
			free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
			free(buf);
			free(mbuf);
			a1logd(p->log,3,"munki_imp_measure dak measure 1 failed\n");
			return ev;
		}

		ENDPLOT
	}
	/* Take a measurement reading using the current mode. */
	/* Converts to completely processed output readings. */

	a1logd(p->log,3,"Do main measurement reading\n");

	/* Indicate to the user that they can now scan the instrument, */
	/* after a little delay that allows for the instrument reaction time. */
	if (s->scan) {
		int delay = 100 + (int)(invsampt * 1000.0 + 0.9);
		if (p->eventcallback != NULL) {
			issue_scan_ready((inst *)p, delay);
		} else {
			/* delay then 1KHz for 200 msec */
			msec_beep(delay, 1000, 200);
		}
	}

	/* Retry loop in case a display read is saturated */
	for (;;) {

		/* Trigger measure and gather raw readings */
		if ((ev = munki_read_patches_1(p, ninvmeas, nummeas, maxnummeas, &s->inttime, s->gainmode,
		                                       &nmeasuered, mbuf, mbsize)) != MUNKI_OK) {
			free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
			if (buf != NULL)
				free(buf);
			free(mbuf);
			a1logd(p->log,3,"munki_imp_measure failed at munki_read_patches_1\n");
			return ev;
		}

		/* Complete processing of dark readings now that main measurement has been taken */
		if (s->reflective) {
			a1logd(p->log,3,"Calling black calibration_2 calc with nummeas %d, inttime %f, gainmode %d\n", nummeas, s->inttime,s->gainmode);
			if ((ev = munki_dark_measure_2(p, s->dark_data, nummeas, s->inttime,
			                                  s->gainmode, buf, bsize)) != MUNKI_OK) {
				free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
				free(buf);
				free(mbuf);
				a1logd(p->log,3,"munki_imp_measure failed at munki_dark_measure_2\n");
				return ev;
			}
			s->dark_valid = 1;
			s->dark_int_time = s->inttime;
			s->dark_gain_mode = s->gainmode;
			free(buf);
		}

		/* Process the raw measurement readings into final spectral readings */
		ev = munki_read_patches_2(p, &duration, specrd, nvals, s->inttime, s->gainmode,
		                                              ninvmeas, nmeasuered, mbuf, mbsize);
		/* Special case display mode read. If the sensor is saturated, and */
		/* we haven't already done so, switch to the alternate integration time */
		/* and try again. */
		if (s->emiss && !s->scan && !s->adaptive
		 && ev == MUNKI_RD_SENSORSATURATED
		 && s->dispswap < 2) {
			double *tt, tv;

			if (s->dispswap == 0) {
				a1logd(p->log,3,"Switching to alternate display integration time %f seconds\n",s->dark_int_time2);
				tv = s->inttime; s->inttime = s->dark_int_time2; s->dark_int_time2 = tv;
				tt = s->dark_data; s->dark_data = s->dark_data2; s->dark_data2 = tt;
				s->dispswap = 1;
			} else if (s->dispswap == 1) {
				a1logd(p->log,3,"Switching to 2nd alternate display integration time %f seconds\n",s->dark_int_time3);
				/* Undo first swap */
				tv = s->inttime; s->inttime = s->dark_int_time2; s->dark_int_time2 = tv;
				tt = s->dark_data; s->dark_data = s->dark_data2; s->dark_data2 = tt;
				/* Do 2nd swap */
				tv = s->inttime; s->inttime = s->dark_int_time3; s->dark_int_time3 = tv;
				tt = s->dark_data; s->dark_data = s->dark_data3; s->dark_data3 = tt;
				s->dispswap = 2;
			}
			/* Recompute number of measurements and realloc measurement buffer */
			free(mbuf);
			nummeas = munki_comp_nummeas(p, s->wreadtime, s->inttime);
			maxnummeas = munki_comp_nummeas(p, s->maxscantime, s->inttime);
			if (maxnummeas < nummeas)
				maxnummeas = nummeas;
			mbsize = m->nsen * 2 * maxnummeas;
			if ((mbuf = (unsigned char *)malloc(sizeof(unsigned char) * mbsize)) == NULL) {
				free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
				a1logd(p->log,1,"munki_imp_measure malloc %d bytes failed (7)\n",mbsize);
				return MUNKI_INT_MALLOC;
			}
			continue;			/* Do the measurement again */
		}

		if (ev != MUNKI_OK) {
			free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
			free(mbuf);
			a1logd(p->log,3,"munki_imp_measure failed at munki_read_patches_2\n");
			return ev;
		}
		break;		/* Don't repeat */
	}
	free(mbuf);

	/* Transfer spectral and convert to XYZ */
	if ((ev = munki_conv2XYZ(p, vals, nvals, specrd, clamp)) != MUNKI_OK) {
		free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);
		a1logd(p->log,3,"munki_imp_measure failed at munki_conv2XYZ\n");
		return ev;
	}
	free_dmatrix(specrd, 0, nvals-1, 0, m->nwav-1);

	if (nvals > 0)
		vals[0].duration = duration;	/* Possible flash duration */
	
	a1logd(p->log,3,"munki_imp_measure successful return\n");
	if (user_trig)
		return MUNKI_USER_TRIG;
	return ev; 
}

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

	Determining the refresh rate for a refresh type display.

	This is easy when the max sample rate of the i1 is above
	the nyquist of the display, and will always be the case
	for the range we are prepared to measure (up to 100Hz)
	when using an Rev B, D or E, but is a problem for the
	rev A and ColorMunki, which can only sample at 113Hz.

	We work around this problem by detecting when
	we are measuring an alias of the refresh rate, and
	average the aliasing corrected measurements.

	If there is no aparent refresh, or the refresh rate is not determinable,
	return a period of 0.0 and inst_ok;
*/

munki_code munki_measure_rgb(munki *p, double *inttime, double *rgb);

#ifndef PSRAND32L 
# define PSRAND32L(S) ((S) * 1664525L + 1013904223L)
#endif
#undef FREQ_SLOW_PRECISE	/* [und] Interpolate then autocorrelate, else autc & filter */
#define NFSAMPS 80		/* Number of samples to read */
#define NFMXTIME 6.0	/* Maximum time to take (2000 == 6) */
#define PBPMS 20		/* bins per msec */
#define PERMIN ((1000 * PBPMS)/40)	/* 40 Hz */
#define PERMAX ((1000 * PBPMS)/4)	/* 4 Hz*/
#define NPER (PERMAX - PERMIN + 1)
#define PWIDTH (8 * PBPMS)			/* 8 msec bin spread to look for peak in */
#define MAXPKS 20					/* Number of peaks to find */
#define TRIES 8	 					/* Number of different sample rates to try */

munki_code munki_imp_meas_refrate(
	munki *p,
	double *ref_rate
) {
	munki_code ev = MUNKI_OK;
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	int i, j, k, mm;
	double **multimeas;			/* Spectral measurements */
	int nummeas;
	double rgbw[3] = { 610.0, 520.0, 460.0 };
	double ucalf = 1.0;				/* usec_time calibration factor */
	double inttime;
	static unsigned int randn = 0x12345678;
	struct {
		double sec;
		double rgb[3];
	} samp[NFSAMPS * 2];
	int nfsamps;			/* Actual samples read */
	double minv[3];			/* Minimum reading */
	double maxv[3];			/* Maximum reading */
	double maxt;			/* Time range */
#ifdef FREQ_SLOW_PRECISE
	int nbins;
	double *bins[3];		/* PBPMS sample bins */
#else
	double tcorr[NPER];		/* Temp for initial autocorrelation */
	int ntcorr[NPER];		/* Number accumulated */
#endif
	double corr[NPER];		/* Filtered correlation for each period value */
	double mincv, maxcv;	/* Max and min correlation values */
	double crange;			/* Correlation range */
	double peaks[MAXPKS];	/* Peak wavelength */
	double peakh[MAXPKS];	/* Peak heighheight */
	int npeaks;				/* Number of peaks */
	double pval;			/* Period value */
	double rfreq[TRIES];	/* Computed refresh frequency for each try */
	double rsamp[TRIES];	/* Sampling rate used to measure frequency */
	int tix = 0;			/* try index */

	a1logd(p->log,2,"munki_imp_meas_refrate called\n");

	if (ref_rate != NULL)
		*ref_rate = 0.0;

	if (!s->emiss) {
		a1logd(p->log,2,"munki_imp_meas_refrate not in emissive mode\n");
		return MUNKI_UNSUPPORTED;
	}

	/* Try different sample rates. Need 3 out of 8 to proceed. */
	for (mm = 0; mm < TRIES; mm++) {
		rfreq[mm] = 0.0;
		npeaks = 0;			/* Number of peaks */
		nummeas = NFSAMPS;
		multimeas = dmatrix(0, nummeas-1, -1, m->nwav-1);

		if (mm == 0)
			inttime = m->min_int_time;
		else {
			double rval, dmm;
			randn = PSRAND32L(randn);
			rval = (double)randn/4294967295.0;
			dmm = ((double)mm + rval - 0.5)/(TRIES - 0.5);
			inttime = m->min_int_time * (1.0 + dmm * 0.80);
		}

		if ((ev = munki_read_patches_all(p, multimeas, nummeas, &inttime, 0)) != inst_ok) {
			free_dmatrix(multimeas, 0, nummeas-1, 0, m->nwav-1);
			return ev;
		} 

		rsamp[tix] = 1.0/inttime;

		/* Convert the samples to RGB */
		for (i = 0; i < nummeas && i < NFSAMPS; i++) {
			samp[i].sec = i * inttime;
			samp[i].rgb[0] = samp[i].rgb[1] = samp[i].rgb[2] = 0.0;
			for (j = 0; j < m->nwav; j++) {
				double wl = XSPECT_WL(m->wl_short, m->wl_long, m->nwav, j);

//printf("~1 multimeas %d %d = %f\n",i, j, multimeas[i][j]);
				for (k = 0; k < 3; k++) {
					double tt = (double)(wl - rgbw[k]);
					tt = (40.0 - fabs(tt))/40.0;
					if (tt < 0.0)
						tt = 0.0;
					samp[i].rgb[k] += sqrt(tt) * multimeas[i][j];
				}
			}
		}
		nfsamps = i;

		a1logd(p->log, 3, "munki_measure_refresh: Read %d samples for refresh calibration\n",nfsamps);

#ifdef NEVER
		/* Plot the raw sensor values */
		{
			double xx[NFSAMPS];
			double y1[NFSAMPS];
			double y2[NFSAMPS];
			double y3[NFSAMPS];

			for (i = 0; i < nfsamps; i++) {
				xx[i] = samp[i].sec;
				y1[i] = samp[i].rgb[0];
				y2[i] = samp[i].rgb[1];
				y3[i] = samp[i].rgb[2];
//			printf("%d: %f -> %f\n",i,samp[i].sec, samp[i].rgb[0]);
			}
			plot_msg("Fast scan sensor values and time (sec)\n");
			do_plot6(xx, y1, y2, y3, NULL, NULL, NULL, nfsamps);
		}
#endif

		/* Locate the smallest values and maximum time */
		maxt = -1e6;
		minv[0] = minv[1] = minv[2] = 1e20;
		maxv[0] = maxv[1] = maxv[2] = -11e20;
		for (i = nfsamps-1; i >= 0; i--) {
			if (samp[i].sec > maxt)
				maxt = samp[i].sec;
			for (j = 0; j < 3; j++) {
				if (samp[i].rgb[j] < minv[j])
					minv[j] = samp[i].rgb[j]; 
				if (samp[i].rgb[j] > maxv[j])
					maxv[j] = samp[i].rgb[j]; 
			}
		}
		/* Re-zero the sample times, and normalise the readings */
		for (i = nfsamps-1; i >= 0; i--) {
			samp[i].sec -= samp[0].sec; 
			samp[i].sec *= ucalf;
			if (samp[i].sec > maxt)
				maxt = samp[i].sec;
			for (j = 0; j < 3; j++) {
				samp[i].rgb[j] -= minv[j];
			}
		}

#ifdef FREQ_SLOW_PRECISE	/* Interpolate then autocorrelate */

		/* Create PBPMS bins and interpolate readings into them */
		nbins = 1 + (int)(maxt * 1000.0 * PBPMS + 0.5);
		for (j = 0; j < 3; j++) {
			if ((bins[j] = (double *)calloc(sizeof(double), nbins)) == NULL) {
				a1loge(p->log, inst_internal_error, "munki_measure_refresh: malloc failed\n");
				return MUNKI_INT_MALLOC;
			}
		}

		/* Do the interpolation */
		for (k = 0; k < (nfsamps-1); k++) {
			int sbin, ebin;
			sbin = (int)(samp[k].sec * 1000.0 * PBPMS + 0.5);
			ebin = (int)(samp[k+1].sec * 1000.0 * PBPMS + 0.5);
			for (i = sbin; i <= ebin; i++) {
				double bl;
#if defined(__APPLE__) && defined(__POWERPC__)
				gcc_bug_fix(i);
#endif
				bl = (i - sbin)/(double)(ebin - sbin);	/* 0.0 to 1.0 */
				for (j = 0; j < 3; j++) {
					bins[j][i] = (1.0 - bl) * samp[k].rgb[j] + bl * samp[k+1].rgb[j];
				}
			} 
		}

#ifdef NEVER
		/* Plot interpolated values */
		{
			double *xx;
			double *y1;
			double *y2;
			double *y3;

			xx = malloc(sizeof(double) * nbins);
			y1 = malloc(sizeof(double) * nbins);
			y2 = malloc(sizeof(double) * nbins);
			y3 = malloc(sizeof(double) * nbins);

			if (xx == NULL || y1 == NULL || y2 == NULL || y3 == NULL) {
				a1loge(p->log, inst_internal_error, "munki_measure_refresh: malloc failed\n");
				for (j = 0; j < 3; j++)
					free(bins[j]);
				return MUNKI_INT_MALLOC;
			}
			for (i = 0; i < nbins; i++) {
				xx[i] = i / (double)PBPMS;			/* msec */
				y1[i] = bins[0][i];
				y2[i] = bins[1][i];
				y3[i] = bins[2][i];
			}
			plot_msg("Interpolated fast scan sensor values and time (msec) for inttime %f\n",inttime);
			do_plot6(xx, y1, y2, y3, NULL, NULL, NULL, nbins);

			free(xx);
			free(y1);
			free(y2);
			free(y3);
		}
#endif /* PLOT_REFRESH */

		/* Compute auto-correlation at 1/PBPMS msec intervals */
		/* from 25 msec (40Hz) to 100msec (10 Hz) */
		mincv = 1e48, maxcv = -1e48;
		for (i = 0; i < NPER; i++) {
			int poff = PERMIN + i;		/* Offset to corresponding sample */

			corr[i] = 0;
			for (k = 0; (k + poff) < nbins; k++) {
				corr[i] += bins[0][k] * bins[0][k + poff]
				        +  bins[1][k] * bins[1][k + poff]
				        +  bins[2][k] * bins[2][k + poff];
			}
			corr[i] /= (double)k;		/* Normalize */

			if (corr[i] > maxcv)
				maxcv = corr[i];
			if (corr[i] < mincv)
				mincv = corr[i];
		}
		/* Free the bins */
		for (j = 0; j < 3; j++)
			free(bins[j]);

#else /* !FREQ_SLOW_PRECISE  Fast - autocorrellate then filter */

	/* Upsample by a factor of 2 */
	for (i = nfsamps-1; i >= 0; i--) {
		j = 2 * i;
		samp[j].sec = samp[i].sec;
		samp[j].rgb[0] = samp[i].rgb[0];
		samp[j].rgb[1] = samp[i].rgb[1];
		samp[j].rgb[2] = samp[i].rgb[2];
		if (i > 0) {
			j--;
			samp[j].sec = 0.5 * (samp[i].sec + samp[i-1].sec);
			samp[j].rgb[0] = 0.5 * (samp[i].rgb[0] + samp[i-1].rgb[0]);
			samp[j].rgb[1] = 0.5 * (samp[i].rgb[1] + samp[i-1].rgb[1]);
			samp[j].rgb[2] = 0.5 * (samp[i].rgb[2] + samp[i-1].rgb[2]);
		}
	}
	nfsamps = 2 * nfsamps - 1;

	/* Do point by point correllation of samples */
	for (i = 0; i < NPER; i++) {
		tcorr[i] = 0.0;
		ntcorr[i] = 0;
	}

	for (j = 0; j < (nfsamps-1); j++) {

		for (k = j+1; k < nfsamps; k++) {
			double del, cor;
			int bix;

			del = samp[k].sec - samp[j].sec;
			bix = (int)(del * 1000.0 * PBPMS + 0.5);
			if (bix < PERMIN)
				continue;
			if (bix > PERMAX)
				break;
			bix -= PERMIN;
		
			cor = samp[j].rgb[0] * samp[k].rgb[0]
	            + samp[j].rgb[1] * samp[k].rgb[1]
	            + samp[j].rgb[2] * samp[k].rgb[2];

//printf("~1 j %d k %d, del %f bix %d cor %f\n",j,k,del,bix,cor);
			tcorr[bix] += cor;
			ntcorr[bix]++;
		} 
	}
	/* Divide out count and linearly interpolate */
	j = 0;
	for (i = 0; i < NPER; i++) {
		if (ntcorr[i] > 0) {
			tcorr[i] /= ntcorr[i];
			if ((i - j) > 1) {
				if (j == 0) {
					for (k = j; k < i; k++)
						tcorr[k] = tcorr[i];

				} else {		/* Linearly interpolate from last value */
					double ww = (double)i-j;
					for (k = j+1; k < i; k++) {
						double bl = (k-j)/ww;
						tcorr[k] = (1.0 - bl) * tcorr[j] + bl * tcorr[i];
					}
				}
			}
			j = i;
		}
	}
	if (j < (NPER-1)) {
		for (k = j+1; k < NPER; k++) {
			tcorr[k] = tcorr[j];
		}
	}

#ifdef PLOT_REFRESH
	/* Plot unfiltered auto correlation */
	{
		double xx[NPER];
		double y1[NPER];

		for (i = 0; i < NPER; i++) {
			xx[i] = (i + PERMIN) / (double)PBPMS;			/* msec */
			y1[i] = tcorr[i];
		}
		plot_msg("Unfiltered auto correlation (msec)\n");
		do_plot6(xx, y1, NULL, NULL, NULL, NULL, NULL, NPER);
	}
#endif /* PLOT_REFRESH */

	/* Apply a gausian filter */
#define FWIDTH 100
	{
		double gaus_[2 * FWIDTH * PBPMS + 1];
		double *gaus = &gaus_[FWIDTH * PBPMS];
		double bb = 1.0/pow(2, 5.0);
		double fw = inttime * 1000.0;
		int ifw;

//printf("~1 sc = %f = %f msec\n",1.0/inttime, fw);
//printf("~1 fw = %f, ifw = %d\n",fw,ifw);

		fw *= 0.9;
		ifw = (int)ceil(fw * PBPMS);
		if (ifw > FWIDTH * PBPMS)
			error("munki: Not enough space for lanczos 2 filter");
		for (j = -ifw; j <= ifw; j++) {
			double x, y;
			x = j/(PBPMS * fw);
			if (fabs(x) > 1.0)
				y = 0.0;
			else
				y = 1.0/pow(2, 5.0 * x * x) - bb;
			gaus[j] = y;
//printf("~1 gaus[%d] = %f\n",j,y);
		}

		for (i = 0; i < NPER; i++) {
			double sum = 0.0;
			double wght = 0.0;
			
			for (j = -ifw; j <= ifw; j++) {
				double w;
				int ix = i + j;
				if (ix < 0)
					ix = -ix;
				if (ix > (NPER-1))
					ix = 2 * NPER-1 - ix;
				w = gaus[j];
				sum += w * tcorr[ix];
				wght += w;
			}
//printf("~1 corr[%d] wgt = %f\n",i,wght);
			corr[i] = sum / wght;
		}
	}

	/* Compute min & max */
	mincv = 1e48, maxcv = -1e48;
	for (i = 0; i < NPER; i++) {
		if (corr[i] > maxcv)
			maxcv = corr[i];
		if (corr[i] < mincv)
			mincv = corr[i];
	}

#endif /* !FREQ_SLOW_PRECISE  Fast - autocorrellate then filter */

		crange = maxcv - mincv;
		a1logd(p->log,3,"Correlation value range %f - %f = %f = %f%%\n",mincv, maxcv,crange, 100.0 * (maxcv-mincv)/maxcv);

#ifdef PLOT_REFRESH
		/* Plot this measuremnts auto correlation */
		{
			double xx[NPER];
			double y1[NPER];
		
			for (i = 0; i < NPER; i++) {
				xx[i] = (i + PERMIN) / (double)PBPMS;			/* msec */
				y1[i] = corr[i];
			}
			plot_msg("Auto correlation (msec)\n");
			do_plot6(xx, y1, NULL, NULL, NULL, NULL, NULL, NPER);
		}
#endif /* PLOT_REFRESH */

#define PFDB 4	// normally 4
		/* If there is sufficient level and distict correlations */
		if (crange/maxcv >= 0.1) {

			a1logd(p->log,PFDB,"Searching for peaks\n");

			/* Locate all the peaks starting at the longest correllation */
			for (i = (NPER-1-PWIDTH); i >= 0 && npeaks < MAXPKS; i--) {
				double v1, v2, v3;
				v1 = corr[i];
				v2 = corr[i + PWIDTH/2];	/* Peak */
				v3 = corr[i + PWIDTH];

				if (fabs(v3 - v1)/crange < 0.05
				 && (v2 - v1)/crange > 0.025
				 && (v2 - v3)/crange > 0.025
				 && (v2 - mincv)/crange > 0.5) {
					double pkv;			/* Peak value */
					int pki;			/* Peak index */
					double ii, bl;

#ifdef PLOT_REFRESH
					a1logd(p->log,PFDB,"Max between %f and %f msec\n",
					       (i + PERMIN)/(double)PBPMS,(i + PWIDTH + PERMIN)/(double)PBPMS);
#endif

					/* Locate the actual peak */
					pkv = -1.0;
					pki = 0;
					for (j = i; j < (i + PWIDTH); j++) {
						if (corr[j] > pkv) {
							pkv = corr[j];
							pki = j;
						}
					}
#ifdef PLOT_REFRESH
					a1logd(p->log,PFDB,"Peak is at %f msec, %f corr\n", (pki + PERMIN)/(double)PBPMS, pkv);
#endif

					/* Interpolate the peak value for higher precision */
					/* j = bigest */
					if (corr[pki-1] > corr[pki+1])  {
						j = pki-1;
						k = pki+1;
					} else {
						j = pki+1;
						k = pki-1;
					}
					bl = (corr[pki] - corr[j])/(corr[pki] - corr[k]);
					bl = (bl + 1.0)/2.0;
					ii = bl * pki + (1.0 - bl) * j;
					pval = (ii + PERMIN)/(double)PBPMS;
#ifdef PLOT_REFRESH
					a1logd(p->log,PFDB,"Interpolated peak is at %f msec\n", pval);
#endif
					peaks[npeaks] = pval;
					peakh[npeaks] = corr[pki];
					npeaks++;

					i -= PWIDTH;
				}
#ifdef NEVER
				if (v2 > v1 && v2 > v3) {
					printf("Peak rehjected:\n");
					printf("(v3 - v1)/crange = %f < 0.05 ?\n",fabs(v3 - v1)/crange);
					printf("(v2 - v1)/crange = %f > 0.025 ?\n",(v2 - v1)/crange);
					printf("(v2 - v3)/crange = %f > 0.025 ?\n",(v2 - v3)/crange);
					printf("(v2 - mincv)/crange = %f > 0.5 ?\n",(v2 - mincv)/crange);
				}
#endif
			}
			a1logd(p->log,3,"Number of peaks located = %d\n",npeaks);

		} else {
			a1logd(p->log,3,"All rejected, crange/maxcv = %f < 0.06\n",crange/maxcv);
		}
#undef PFDB

		a1logd(p->log,3,"Number of peaks located = %d\n",npeaks);

		if (npeaks > 1) {		/* Compute aparent refresh rate */
			int nfails;
			double div, avg, ano;
			/* Try and locate a common divisor amongst all the peaks. */
			/* This is likely to be the underlying refresh rate. */
			for (k = 0; k < npeaks; k++) {
				for (j = 1; j < 25; j++) {
					avg = ano = 0.0;
					div = peaks[k]/(double)j;
					if (div < 5.0)
						continue;		/* Skip anything higher than 200Hz */ 
//printf("~1 trying %f Hz\n",1000.0/div);
					for (nfails = i = 0; i < npeaks; i++) {
						double rem, cnt;
	
						rem = peaks[i]/div;
						cnt = floor(rem + 0.5);
						rem = fabs(rem - cnt);
	
#ifdef PLOT_REFRESH
						a1logd(p->log, 3, "remainder for peak %d = %f\n",i,rem);
#endif
						if (rem > 0.06) {
							if (++nfails > 2)
								break;				/* Fail this divisor */
						} else {
							avg += peaks[i];		/* Already weighted by cnt */
							ano += cnt;
						}
					}
	
					if (nfails == 0 || (nfails <= 2 && npeaks >= 6))
						break;		/* Success */
					/* else go and try a different divisor */
				}
				if (j < 25)
					break;			/* Success - found common divisor */
			}
			if (k >= npeaks) {
				a1logd(p->log,3,"Failed to locate common divisor\n");
			
			} else {
				pval = 0.001 * avg/ano;
				if (pval < inttime) {
					a1logd(p->log,3,"Discarding frequency %f > sample rate %f\n",1.0/pval, 1.0/inttime);
				} else {
					pval = 1.0/pval;		/* Convert to frequency */
					rfreq[tix++] = pval;
					a1logd(p->log,3,"Located frequency %f sum %f dif %f\n",pval, pval + 1.0/inttime, fabs(pval - 1.0/inttime));
				}
			}
		}
	}

	if (tix >= 3) {

		for (mm = 0; mm < tix; mm++) {
			a1logd(p->log, 3, "Try %d, samp %f Hz, Meas %f Hz, Sum %f Hz, Dif %f Hz\n",mm,rsamp[mm],rfreq[mm], rsamp[mm] + rfreq[mm], fabs(rsamp[mm] - rfreq[mm]));
		}
	
		/* Decide if we are above the nyquist, or whether */
		/* we have aliases of the fundamental */
		{
			double brange = 1e38;
			double brate = 0.0;
			int bsplit = -1;
			double min, max, avg, range;
			int split, mul, niia;
			
			/* Compute fundamental and sub aliases at all possible splits. */
			/* Skip the reading at the split. */
			for (split = tix; split >= -1; split--) {
				min = 1e38; max = -1e38; avg = 0.0; niia = 0;
				for (mm = 0; mm < tix; mm++) {
					double alias;

					if (mm == split)
						continue;
					if (mm < split)
						alias = rfreq[mm];
					else
						alias = fabs(rsamp[mm] - rfreq[mm]);

					avg += alias;
					niia++;
	
					if (alias < min)
						min = alias;
					if (alias > max)
						max = alias;
				}
				avg /= (double)niia;
				range = (max - min)/(max + min);
//printf("~1 split %d avg = %f, range = %f\n",split,avg,range);
				if (range < brange) {
					brange = range;
					brate = avg;
					bsplit = split;
				}
			}

			/* Compute sub and add aliases at all possible splits */
			/* Skip the reading at the split. */
			for (split = tix; split >= -1; split--) {
				min = 1e38; max = -1e38; avg = 0.0; niia = 0;
				for (mm = 0; mm < tix; mm++) {
					double alias;

					if (mm == split)
						continue;
					if (mm < split)
						alias = fabs(rsamp[mm] - rfreq[mm]);
					else
						alias = rsamp[mm] + rfreq[mm];

					avg += alias;
					niia++;
	
					if (alias < min)
						min = alias;
					if (alias > max)
						max = alias;
				}
				avg /= (double)niia;
				range = (max - min)/(max + min);
//printf("~1 split %d avg = %f, range = %f\n",100 + split,avg,range);
				if (range < brange) {
					brange = range;
					brate = avg;
					bsplit = 100 + split;
				}
			}

			a1logd(p->log, 3, "Selected split %d range %f\n",bsplit,brange);
			
			/* Hmm. Could reject result and re-try if brange is too large ? ( > 0.005 ?) */
		
			if (brange > 0.05) {
				a1logd(p->log, 3, "Readings are too inconsistent (brange %.1f%%) - should retry ?\n",brange * 100.0);
			} else { 
				if (ref_rate != NULL)
					*ref_rate = brate;
				a1logd(p->log, 1, "Measured refresh rate %f Hz\n",brate);
				return MUNKI_OK;
			}
		}
	} else {
		a1logd(p->log, 3, "Not enough tries (%d) succeeded to determine refresh rate\n",tix);
	}

	return MUNKI_RD_NOREFR_FOUND; 
}
#undef NFSAMPS 
#undef PBPMS
#undef PERMIN
#undef PERMAX
#undef NPER
#undef PWIDTH

/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Save the calibration for all modes, stored on local system */

#ifdef ENABLE_NONVCAL

/* non-volatile save/restor state to/from a file */
typedef struct {
	int ef;				/* Error flag, 1 = write failed, 2 = close failed */
	ORD32 chsum;		/* Checksum */
} mknonv;

static void update_chsum(mknonv *x, unsigned char *p, int nn) {
	int i;
	for (i = 0; i < nn; i++, p++)
		x->chsum = ((x->chsum << 5) | (((1 << 5)-1) & (x->chsum >> (32-5)))) + *p;
}

/* Write an array of chars to the file. Set the error flag to nz on error */
static void write_chars(mknonv *x, FILE *fp, char *dp, int n) {

	if (fwrite((void *)dp, sizeof(char), n, fp) != n) {
		x->ef = 1;
	} else {
		update_chsum(x, (unsigned char *)dp, n * sizeof(char));
	}
}

/* Write an array of ints to the file. Set the error flag to nz on error */
static void write_ints(mknonv *x, FILE *fp, int *dp, int n) {

	if (fwrite((void *)dp, sizeof(int), n, fp) != n) {
		x->ef = 1;
	} else {
		update_chsum(x, (unsigned char *)dp, n * sizeof(int));
	}
}

/* Write an array of doubles to the file. Set the error flag to nz on error */
static void write_doubles(mknonv *x, FILE *fp, double *dp, int n) {

	if (fwrite((void *)dp, sizeof(double), n, fp) != n) {
		x->ef = 1;
	} else {
		update_chsum(x, (unsigned char *)dp, n * sizeof(double));
	}
}

/* Write an array of time_t's to the file. Set the error flag to nz on error */
/* (This will cause file checksum fail if different executables on the same */
/*  system have different time_t values) */
static void write_time_ts(mknonv *x, FILE *fp, time_t *dp, int n) {

	if (fwrite((void *)dp, sizeof(time_t), n, fp) != n) {
		x->ef = 1;
	} else {
		update_chsum(x, (unsigned char *)dp, n * sizeof(time_t));
	}
}

/* Read an array of ints from the file. Set the error flag to nz on error */
static void read_ints(mknonv *x, FILE *fp, int *dp, int n) {

	if (fread((void *)dp, sizeof(int), n, fp) != n) {
		x->ef = 1;
	} else {
		update_chsum(x, (unsigned char *)dp, n * sizeof(int));
	}
}

/* Read an array of chars from the file. Set the error flag to nz on error */
static void read_chars(mknonv *x, FILE *fp, char *dp, int n) {

	if (fread((void *)dp, sizeof(char), n, fp) != n) {
		x->ef = 1;
	} else {
		update_chsum(x, (unsigned char *)dp, n * sizeof(char));
	}
}


/* Read an array of doubles from the file. Set the error flag to nz on error */
static void read_doubles(mknonv *x, FILE *fp, double *dp, int n) {

	if (fread((void *)dp, sizeof(double), n, fp) != n) {
		x->ef = 1;
	} else {
		update_chsum(x, (unsigned char *)dp, n * sizeof(double));
	}
}

/* Read an array of time_t's from the file. Set the error flag to nz on error */
/* (This will cause file checksum fail if different executables on the same */
/*  system have different time_t values) */
static void read_time_ts(mknonv *x, FILE *fp, time_t *dp, int n) {

	if (fread((void *)dp, sizeof(time_t), n, fp) != n) {
		x->ef = 1;
	} else {
		update_chsum(x, (unsigned char *)dp, n * sizeof(time_t));
	}
}

munki_code munki_save_calibration(munki *p) {
	munkiimp *m = (munkiimp *)p->m;
	munki_code ev = MUNKI_OK;
	munki_state *s;
	int i;
	char nmode[10];
	char cal_name[100];		/* Name */
	char **cal_paths = NULL;
	int no_paths = 0;
	FILE *fp;
	mknonv x;
	int ss;
	int argyllversion = ARGYLL_VERSION;

	strcpy(nmode, "w");
#if !defined(O_CREAT) && !defined(_O_CREAT)
# error "Need to #include fcntl.h!"
#endif
#if defined(O_BINARY) || defined(_O_BINARY)
	strcat(nmode, "b");
#endif

	sprintf(cal_name, "ArgyllCMS/.mk_%s.cal", m->serno);
	if ((no_paths = xdg_bds(NULL, &cal_paths, xdg_cache, xdg_write, xdg_user, xdg_none,
		                                                                      cal_name)) < 1) {
		a1logd(p->log,1,"munki_save_calibration xdg_bds returned no paths\n");
		return MUNKI_INT_CAL_SAVE;
	}

	a1logd(p->log,3,"munki_save_calibration saving to file '%s'\n",cal_paths[0]);

	if (create_parent_directories(cal_paths[0])
	 || (fp = fopen(cal_paths[0], nmode)) == NULL) {
		a1logd(p->log,3,"munki_save_calibration failed to open file for writing\n");
		xdg_free(cal_paths, no_paths);
		return MUNKI_INT_CAL_SAVE;
	}
	
	x.ef = 0;
	x.chsum = 0;

	/* A crude structure signature */
	ss = sizeof(munki_state) + sizeof(munkiimp);

	/* Some file identification */
	write_ints(&x, fp, &argyllversion, 1);
	write_ints(&x, fp, &ss, 1);
	write_chars(&x, fp, m->serno, 17);
	write_ints(&x, fp, &m->nraw, 1);
	write_ints(&x, fp, (int *)&m->nwav1, 1);
	write_ints(&x, fp, (int *)&m->nwav2, 1);

	/* For each mode, save the calibration if it's valid */
	for (i = 0; i < mk_no_modes; i++) {
		s = &m->ms[i];

		/* Mode identification */
		write_ints(&x, fp, &s->emiss, 1);
		write_ints(&x, fp, &s->trans, 1);
		write_ints(&x, fp, &s->reflective, 1);
		write_ints(&x, fp, &s->scan, 1);
		write_ints(&x, fp, &s->flash, 1);
		write_ints(&x, fp, &s->ambient, 1);
		write_ints(&x, fp, &s->projector, 1);
		write_ints(&x, fp, &s->adaptive, 1);

		/* Configuration calibration is valid for */
		write_ints(&x, fp, &s->gainmode, 1);
		write_doubles(&x, fp, &s->inttime, 1);

		/* Calibration information */
		write_ints(&x, fp, &s->dark_valid, 1);
		write_time_ts(&x, fp, &s->ddate, 1);
		write_doubles(&x, fp, &s->dark_int_time, 1);
		write_doubles(&x, fp, s->dark_data-1, m->nraw+1);
		write_doubles(&x, fp, &s->dark_int_time2, 1);
		write_doubles(&x, fp, s->dark_data2-1, m->nraw+1);
		write_doubles(&x, fp, &s->dark_int_time3, 1);
		write_doubles(&x, fp, s->dark_data3-1, m->nraw+1);
		write_ints(&x, fp, &s->dark_gain_mode, 1);

		if (!s->emiss) {
			write_ints(&x, fp, &s->cal_valid, 1);
			write_time_ts(&x, fp, &s->cfdate, 1);
			write_doubles(&x, fp, s->cal_factor1, m->nwav1);
			write_doubles(&x, fp, s->cal_factor2, m->nwav2);
			write_doubles(&x, fp, s->white_data-1, m->nraw+1);
			write_doubles(&x, fp, &s->reftemp, 1);
			write_doubles(&x, fp, s->iwhite_data[0]-1, m->nraw+1);
			write_doubles(&x, fp, s->iwhite_data[1]-1, m->nraw+1);
		}
		
		write_ints(&x, fp, &s->idark_valid, 1);
		write_time_ts(&x, fp, &s->iddate, 1);
		write_doubles(&x, fp, s->idark_int_time, 4);
		write_doubles(&x, fp, s->idark_data[0]-1, m->nraw+1);
		write_doubles(&x, fp, s->idark_data[1]-1, m->nraw+1);
		write_doubles(&x, fp, s->idark_data[2]-1, m->nraw+1);
		write_doubles(&x, fp, s->idark_data[3]-1, m->nraw+1);
	}

	a1logd(p->log,3,"Checksum = 0x%x\n",x.chsum);
	write_ints(&x, fp, (int *)&x.chsum, 1);

	if (fclose(fp) != 0)
		x.ef = 2;

	if (x.ef != 0) {
		a1logd(p->log,3,"Writing calibration file failed with %d\n",x.ef);
		delete_file(cal_paths[0]);
	} else {
		a1logd(p->log,3,"Writing calibration file succeeded\n");
	}
	xdg_free(cal_paths, no_paths);

	return ev;
}

/* Restore the all modes calibration from the local system */
munki_code munki_restore_calibration(munki *p) {
	munkiimp *m = (munkiimp *)p->m;
	munki_code ev = MUNKI_OK;
	munki_state *s, ts;
	int i, j;
	char nmode[10];
	char cal_name[100];		/* Name */
	char **cal_paths = NULL;
	int no_paths = 0;
	FILE *fp;
	mknonv x;
	int argyllversion;
	int ss, nraw, nwav1, nwav2, chsum1, chsum2;
	char serno[17];

	strcpy(nmode, "r");
#if defined(O_BINARY) || defined(_O_BINARY)
	strcat(nmode, "b");
#endif

	sprintf(cal_name, "ArgyllCMS/.mk_%s.cal" SSEPS "color/.mk_%s.cal", m->serno, m->serno);
	if ((no_paths = xdg_bds(NULL, &cal_paths, xdg_cache, xdg_read, xdg_user, xdg_none,
		                                                                     cal_name)) < 1) {
		a1logd(p->log,1,"munki_restore_calibration xdg_bds returned no paths\n");
		return MUNKI_INT_CAL_RESTORE;
	}

	a1logd(p->log,2,"munki_restore_calibration restoring from file '%s'\n",cal_paths[0]);

	/* Check the last modification time */
	{
		struct sys_stat sbuf;

		if (sys_stat(cal_paths[0], &sbuf) == 0) {
			m->lo_secs = time(NULL) - sbuf.st_mtime;
			a1logd(p->log,2,"munki_restore_calibration: %d secs from instrument last open\n",m->lo_secs);
		} else {
			a1logd(p->log,2,"munki_restore_calibration: stat on file failed\n");
		}
	}

	if ((fp = fopen(cal_paths[0], nmode)) == NULL) {
		a1logd(p->log,2,"munki_restore_calibration failed to open file for reading\n");
		xdg_free(cal_paths, no_paths);
		return MUNKI_INT_CAL_RESTORE;
	}
	xdg_free(cal_paths, no_paths);
	
	x.ef = 0;
	x.chsum = 0;

	/* Check the file identification */
	read_ints(&x, fp, &argyllversion, 1);
	read_ints(&x, fp, &ss, 1);
	read_chars(&x, fp, serno, 17);
	read_ints(&x, fp, &nraw, 1);
	read_ints(&x, fp, &nwav1, 1);
	read_ints(&x, fp, &nwav2, 1);
	if (x.ef != 0
	 || argyllversion != ARGYLL_VERSION
	 || ss != (sizeof(munki_state) + sizeof(munkiimp))
	 || strcmp(serno, m->serno) != 0
	 || nraw != m->nraw
	 || nwav1 != m->nwav1
	 || nwav2 != m->nwav2) {
		a1logd(p->log,3,"Identification didn't verify\n");
		goto reserr;
	}

	/* Do a dummy read to check the checksum */
	for (i = 0; i < mk_no_modes; i++) {
		int di;
		double dd;
		time_t dt;
		int emiss, trans, reflective, ambient, projector, scan, flash, adaptive;

		s = &m->ms[i];

		/* Mode identification */
		read_ints(&x, fp, &emiss, 1);
		read_ints(&x, fp, &trans, 1);
		read_ints(&x, fp, &reflective, 1);
		read_ints(&x, fp, &scan, 1);
		read_ints(&x, fp, &flash, 1);
		read_ints(&x, fp, &ambient, 1);
		read_ints(&x, fp, &projector, 1);
		read_ints(&x, fp, &adaptive, 1);

		/* Check the mode identification */
		if (emiss != s->emiss
		 || trans != s->trans
		 || reflective != s->reflective
		 || scan != s->scan
		 || flash != s->flash
		 || ambient != s->ambient
		 || projector != s->projector
		 || adaptive != s->adaptive) {
			a1logd(p->log,3,"Mode config. didn't verify\n");
			goto reserr;
		}

		/* Configuration calibration is valid for */
		read_ints(&x, fp, &di, 1);					/* gainmode */
		read_doubles(&x, fp, &dd, 1);				/* inttime */

		/* Calibration information */
		read_ints(&x, fp, &di, 1);					/* dark_valid */
		read_time_ts(&x, fp, &dt, 1);				/* ddate */
		read_doubles(&x, fp, &dd, 1);				/* dark_int_time */
		for (j = -1; j < m->nraw; j++)
			read_doubles(&x, fp, &dd, 1);			/* dark_data */
		read_doubles(&x, fp, &dd, 1);				/* dark_int_time2 */
		for (j = -1; j < m->nraw; j++)
			read_doubles(&x, fp, &dd, 1);			/* dark_data2 */
		read_doubles(&x, fp, &dd, 1);				/* dark_int_time3 */
		for (j = -1; j < m->nraw; j++)
			read_doubles(&x, fp, &dd, 1);			/* dark_data3 */
		read_ints(&x, fp, &di, 1);					/* dark_gain_mode */

		if (!s->emiss) {
			read_ints(&x, fp, &di, 1);				/* cal_valid */
			read_time_ts(&x, fp, &dt, 1);			/* cfdate */
			for (j = 0; j < m->nwav1; j++)
				read_doubles(&x, fp, &dd, 1);		/* cal_factor1 */
			for (j = 0; j < m->nwav2; j++)
				read_doubles(&x, fp, &dd, 1);		/* cal_factor2 */
			for (j = -1; j < m->nraw; j++)
				read_doubles(&x, fp, &dd, 1);		/* white_data */
			read_doubles(&x, fp, &dd, 1);			/* reftemp */
			for (j = -1; j < m->nraw; j++)
				read_doubles(&x, fp, &dd, 1);		/* iwhite_data[0] */
			for (j = -1; j < m->nraw; j++)
				read_doubles(&x, fp, &dd, 1);		/* iwhite_data[1] */
		}
		
		read_ints(&x, fp, &di, 1);					/* idark_valid */
		read_time_ts(&x, fp, &dt, 1);				/* iddate */
		for (j = 0; j < 4; j++)
			read_doubles(&x, fp, &dd, 1);			/* idark_int_time */
		for (j = -1; j < m->nraw; j++)
			read_doubles(&x, fp, &dd, 1);			/* idark_data[0] */
		for (j = -1; j < m->nraw; j++)
			read_doubles(&x, fp, &dd, 1);			/* idark_data[1] */
		for (j = -1; j < m->nraw; j++)
			read_doubles(&x, fp, &dd, 1);			/* idark_data[2] */
		for (j = -1; j < m->nraw; j++)
			read_doubles(&x, fp, &dd, 1);			/* idark_data[3] */
	}

	chsum1 = x.chsum;
	read_ints(&x, fp, &chsum2, 1);
	
	if (x.ef != 0
	 || chsum1 != chsum2) {
		a1logd(p->log,3,"Checksum didn't verify, got 0x%x, expected 0x%x\n",chsum1, chsum2);
		goto reserr;
	}

	rewind(fp);

	/* Allocate space in temp structure */

	ts.dark_data = dvectorz(-1, m->nraw-1);  
	ts.dark_data2 = dvectorz(-1, m->nraw-1);  
	ts.dark_data3 = dvectorz(-1, m->nraw-1);  
	ts.cal_factor1 = dvectorz(0, m->nwav1-1);
	ts.cal_factor2 = dvectorz(0, m->nwav2-1);
	ts.white_data = dvectorz(-1, m->nraw-1);
	ts.iwhite_data = dmatrixz(0, 2, -1, m->nraw-1);  
	ts.idark_data = dmatrixz(0, 3, -1, m->nraw-1);  

	/* Read the identification */
	read_ints(&x, fp, &argyllversion, 1);
	read_ints(&x, fp, &ss, 1);
	read_chars(&x, fp, m->serno, 17);
	read_ints(&x, fp, &m->nraw, 1);
	read_ints(&x, fp, (int *)&m->nwav1, 1);
	read_ints(&x, fp, (int *)&m->nwav2, 1);

	/* For each mode, save the calibration if it's valid */
	for (i = 0; i < mk_no_modes; i++) {
		s = &m->ms[i];

		/* Mode identification */
		read_ints(&x, fp, &ts.emiss, 1);
		read_ints(&x, fp, &ts.trans, 1);
		read_ints(&x, fp, &ts.reflective, 1);
		read_ints(&x, fp, &ts.scan, 1);
		read_ints(&x, fp, &ts.flash, 1);
		read_ints(&x, fp, &ts.ambient, 1);
		read_ints(&x, fp, &ts.projector, 1);
		read_ints(&x, fp, &ts.adaptive, 1);

		/* Configuration calibration is valid for */
		read_ints(&x, fp, &ts.gainmode, 1);
		read_doubles(&x, fp, &ts.inttime, 1);

		/* Calibration information: */

		/* Static Dark */
		read_ints(&x, fp, &ts.dark_valid, 1);
		read_time_ts(&x, fp, &ts.ddate, 1);
		read_doubles(&x, fp, &ts.dark_int_time, 1);
		read_doubles(&x, fp, ts.dark_data-1, m->nraw+1);
		read_doubles(&x, fp, &ts.dark_int_time2, 1);
		read_doubles(&x, fp, ts.dark_data2-1, m->nraw+1);
		read_doubles(&x, fp, &ts.dark_int_time3, 1);
		read_doubles(&x, fp, ts.dark_data3-1, m->nraw+1);
		read_ints(&x, fp, &ts.dark_gain_mode, 1);

		if (!ts.emiss) {
			/* Reflective */
			read_ints(&x, fp, &ts.cal_valid, 1);
			read_time_ts(&x, fp, &ts.cfdate, 1);
			read_doubles(&x, fp, ts.cal_factor1, m->nwav1);
			read_doubles(&x, fp, ts.cal_factor2, m->nwav2);
			read_doubles(&x, fp, ts.white_data-1, m->nraw+1);
			read_doubles(&x, fp, &ts.reftemp, 1);
			read_doubles(&x, fp, ts.iwhite_data[0]-1, m->nraw+1);
			read_doubles(&x, fp, ts.iwhite_data[1]-1, m->nraw+1);
		}
		
		/* Adaptive Dark */
		read_ints(&x, fp, &ts.idark_valid, 1);
		read_time_ts(&x, fp, &ts.iddate, 1);
		read_doubles(&x, fp, ts.idark_int_time, 4);
		read_doubles(&x, fp, ts.idark_data[0]-1, m->nraw+1);
		read_doubles(&x, fp, ts.idark_data[1]-1, m->nraw+1);
		read_doubles(&x, fp, ts.idark_data[2]-1, m->nraw+1);
		read_doubles(&x, fp, ts.idark_data[3]-1, m->nraw+1);

		/* If the configuration for this mode matches */
		/* that of the calibration, restore the calibration */
		/* for this mode. */
		if (x.ef == 0				/* No read error */
		 &&	s->emiss == ts.emiss
		 && s->trans == ts.trans
		 && s->reflective == ts.reflective
		 && s->scan == ts.scan
		 && s->flash == ts.flash
		 && s->ambient == ts.ambient
		 && s->projector == ts.projector
		 && s->adaptive == ts.adaptive
		 && (s->adaptive || fabs(s->inttime - ts.inttime) < 0.01)
		 && (s->adaptive || fabs(s->dark_int_time - ts.dark_int_time) < 0.01)
		 && (s->adaptive || fabs(s->dark_int_time2 - ts.dark_int_time2) < 0.01)
		 && (s->adaptive || fabs(s->dark_int_time3 - ts.dark_int_time3) < 0.01)
		 && (!s->adaptive || fabs(s->idark_int_time[0] - ts.idark_int_time[0]) < 0.01)
		 && (!s->adaptive || fabs(s->idark_int_time[1] - ts.idark_int_time[1]) < 0.01)
		 && (!s->adaptive || fabs(s->idark_int_time[2] - ts.idark_int_time[2]) < 0.01)
		 && (!s->adaptive || fabs(s->idark_int_time[3] - ts.idark_int_time[3]) < 0.01)
		) {
			/* Copy all the fields read above */
			s->emiss = ts.emiss;
			s->trans = ts.trans;
			s->reflective = ts.reflective;
			s->scan = ts.scan;
			s->flash = ts.flash;
			s->ambient = ts.ambient;
			s->projector = ts.projector;
			s->adaptive = ts.adaptive;

			s->gainmode = ts.gainmode;
			s->inttime = ts.inttime;
			s->dark_valid = ts.dark_valid;
			s->ddate = ts.ddate;
			s->dark_int_time = ts.dark_int_time;
			for (j = -1; j < m->nraw; j++)
				s->dark_data[j] = ts.dark_data[j];
			s->dark_int_time2 = ts.dark_int_time2;
			for (j = -1; j < m->nraw; j++)
				s->dark_data2[j] = ts.dark_data2[j];
			s->dark_int_time3 = ts.dark_int_time3;
			for (j = -1; j < m->nraw; j++)
				s->dark_data3[j] = ts.dark_data3[j];
			s->dark_gain_mode = ts.dark_gain_mode;
			if (!ts.emiss) {
				s->cal_valid = ts.cal_valid;
				s->cfdate = ts.cfdate;
				for (j = 0; j < m->nwav1; j++)
					s->cal_factor1[j] = ts.cal_factor1[j];
				for (j = 0; j < m->nwav2; j++)
					s->cal_factor2[j] = ts.cal_factor2[j];
				for (j = -1; j < m->nraw; j++)
					s->white_data[j] = ts.white_data[j];
				s->reftemp = ts.reftemp;
				for (j = -1; j < m->nraw; j++)
					s->iwhite_data[0][j] = ts.iwhite_data[0][j];
				for (j = -1; j < m->nraw; j++)
					s->iwhite_data[1][j] = ts.iwhite_data[1][j];
			}
			s->idark_valid = ts.idark_valid;
			s->iddate = ts.iddate;
			for (j = 0; j < 4; j++)
				s->idark_int_time[j] = ts.idark_int_time[j];
			for (j = -1; j < m->nraw; j++)
				s->idark_data[0][j] = ts.idark_data[0][j];
			for (j = -1; j < m->nraw; j++)
				s->idark_data[1][j] = ts.idark_data[1][j];
			for (j = -1; j < m->nraw; j++)
				s->idark_data[2][j] = ts.idark_data[2][j];
			for (j = -1; j < m->nraw; j++)
				s->idark_data[3][j] = ts.idark_data[3][j];

		} else {
//printf("~1 mode %d\n",i);
//printf("~1 adaptive = %d\n",s->adaptive);
//printf("~1 innttime %f %f\n",s->inttime, ts.inttime);
//printf("~1 dark_int_time %f %f\n",s->dark_int_time,ts.dark_int_time);
//printf("~1 dark_int_time2 %f %f\n",s->dark_int_time2,ts.dark_int_time2);
//printf("~1 dark_int_time3 %f %f\n",s->dark_int_time3,ts.dark_int_time3);
//printf("~1 idark_int_time0 %f %f\n",s->idark_int_time[0],ts.idark_int_time[0]);
//printf("~1 idark_int_time1 %f %f\n",s->idark_int_time[1],ts.idark_int_time[1]);
//printf("~1 idark_int_time2 %f %f\n",s->idark_int_time[2],ts.idark_int_time[2]);
//printf("~1 idark_int_time3 %f %f\n",s->idark_int_time[3],ts.idark_int_time[3]);
			a1logd(p->log,4,"Not restoring cal for mode %d since params don't match:\n",i);
			a1logd(p->log,4,"emis = %d : %d, trans = %d : %d, ref = %d : %d\n",s->emiss,ts.emiss,s->trans,ts.trans,s->reflective,ts.reflective);
			a1logd(p->log,4,"scan = %d : %d, flash = %d : %d, ambi = %d : %d, proj = %d : %d, adapt = %d : %d\n",s->scan,ts.scan,s->flash,ts.flash,s->ambient,ts.ambient,s->projector,ts.projector,s->adaptive,ts.adaptive);
			a1logd(p->log,4,"inttime = %f : %f\n",s->inttime,ts.inttime);
			a1logd(p->log,4,"darkit1 = %f : %f, 2 = %f : %f, 3 = %f : %f\n",s->dark_int_time,ts.dark_int_time,s->dark_int_time2,ts.dark_int_time2,s->dark_int_time3,ts.dark_int_time3);
			a1logd(p->log,4,"idarkit0 = %f : %f, 1 = %f : %f, 2 = %f : %f, 3 = %f : %f\n",s->idark_int_time[0],ts.idark_int_time[0],s->idark_int_time[1],ts.idark_int_time[1],s->idark_int_time[2],ts.idark_int_time[2],s->idark_int_time[3],ts.idark_int_time[3]);
		}
	}

	/* Free up temporary space */
	free_dvector(ts.dark_data, -1, m->nraw-1);  
	free_dvector(ts.dark_data2, -1, m->nraw-1);  
	free_dvector(ts.dark_data3, -1, m->nraw-1);  
	free_dvector(ts.white_data, -1, m->nraw-1);
	free_dmatrix(ts.iwhite_data, 0, 1, -1, m->nraw-1);  
	free_dmatrix(ts.idark_data, 0, 3, -1, m->nraw-1);  

	free_dvector(ts.cal_factor1, 0, m->nwav1-1);
	free_dvector(ts.cal_factor2, 0, m->nwav2-1);

	a1logd(p->log,3,"munki_restore_calibration done\n");
 reserr:;

	fclose(fp);

	return ev;
}

munki_code munki_touch_calibration(munki *p) {
	munkiimp *m = (munkiimp *)p->m;
	munki_code ev = MUNKI_OK;
	char cal_name[100];		/* Name */
	char **cal_paths = NULL;
	int no_paths = 0;
	int rv;

	sprintf(cal_name, "ArgyllCMS/.mk_%s.cal" SSEPS "color/.mk_%s.cal", m->serno, m->serno);
	if ((no_paths = xdg_bds(NULL, &cal_paths, xdg_cache, xdg_read, xdg_user, xdg_none,
		                                                                     cal_name)) < 1)
		return MUNKI_INT_CAL_TOUCH;

	a1logd(p->log,2,"munki_touch_calibration touching file '%s'\n",cal_paths[0]);

	if ((rv = sys_utime(cal_paths[0], NULL)) != 0) {
		a1logd(p->log,2,"munki_touch_calibration failed with %d\n",rv);
		xdg_free(cal_paths, no_paths);
		return MUNKI_INT_CAL_TOUCH;
	}
	xdg_free(cal_paths, no_paths);

	return ev;
}

#endif /* ENABLE_NONVCAL */


/* ============================================================ */
/* Intermediate routines  - composite commands/processing */

/* Take a dark reference measurement - part 1 */
munki_code munki_dark_measure_1(
	munki *p,
	int nummeas,			/* Number of readings to take */
	double *inttime, 		/* Integration time to use/used */
	int gainmode,			/* Gain mode to use, 0 = normal, 1 = high */
	unsigned char *buf,		/* USB reading buffer to use */
	unsigned int bsize		/* Size of buffer */
) {
	munki_code ev = MUNKI_OK;

	if (nummeas <= 0)
		return MUNKI_INT_ZEROMEASURES;
		
	if ((ev = munki_trigger_one_measure(p, nummeas, inttime, gainmode, 1, 1)) != MUNKI_OK)
		return ev;

	if ((ev = munki_readmeasurement(p, nummeas, 0, buf, bsize, NULL, 1, 1)) != MUNKI_OK)
		return ev;

	return ev;
}

/* Take a dark reference measurement - part 2 */
munki_code munki_dark_measure_2(
	munki *p,
	double *sens,		/* Return array [-1 nraw] of sens values */
	int nummeas,			/* Number of readings to take */
	double inttime, 		/* Integration time to use/used */
	int gainmode,			/* Gain mode to use, 0 = normal, 1 = high */
	unsigned char *buf,		/* raw USB reading buffer to process */
	unsigned int bsize		/* Buffer size to process */
) {
	munki_code ev = MUNKI_OK;
	munkiimp *m = (munkiimp *)p->m;
	double **multimes;		/* Multiple measurement results */
	double darkthresh;		/* Dark threshold */
	double sensavg;			/* Overall average of sensor readings */
	int rv;

	multimes = dmatrix(0, nummeas-1, -1, m->nraw-1);

	/* Take a buffer full of raw readings, and convert them to */
	/* floating point sensor readings. Check for saturation */
	if ((rv = munki_sens_to_raw(p, multimes, NULL, buf, 0, nummeas, m->satlimit, &darkthresh))
		                                                                        != MUNKI_OK) {
		free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
		return rv;
	}

	/* Average a set of measurements into one. */
	/* Return nz if the readings are not consistent */
	/* Return the overall average. */
	rv = munki_average_multimeas(p, sens, multimes, nummeas, &sensavg, darkthresh);     
	free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);

#ifdef PLOT_DEBUG
	a1logd(p->log,4,"dark_measure_2: Avg abs. sensor readings = %f, sh/darkthresh %f\n",sensavg,darkthresh);
	printf("sens data:\n");
	plot_raw(sens);
#endif

	if (rv) {
		a1logd(p->log,3,"munki_dark_measure_2: readings are inconsistent\n");
		return MUNKI_RD_DARKREADINCONS;
	}

	if (sensavg > (2.0 * darkthresh)) {
		a1logd(p->log,3,"munki_dark_measure_2: Average %f is > 2 * darkthresh %f\n",sensavg,darkthresh);
		return MUNKI_RD_DARKNOTVALID;
	}
	return ev;
}

#ifdef DUMP_DARKM
int ddumpdarkm = 0;
#endif

/* Take a dark reference measurement (combined parts 1 & 2) */
munki_code munki_dark_measure(
	munki *p,
	double *raw,			/* Return array [-1 nraw] of raw values */
	int nummeas,			/* Number of readings to take */
	double *inttime, 		/* Integration time to use/used */
	int gainmode			/* Gain mode to use, 0 = normal, 1 = high */
) {
	munkiimp *m = (munkiimp *)p->m;
	munki_code ev = MUNKI_OK;
	unsigned char *buf;		/* Raw USB reading buffer */
	unsigned int bsize;

	a1logd(p->log,3, "munki_dark_measure with inttime %f\n",*inttime);
	bsize = m->nsen * 2 * nummeas;
	if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
		a1logd(p->log,1,"munki_dark_measure malloc %d bytes failed (8)\n",bsize);
		return MUNKI_INT_MALLOC;
	}

	if ((ev = munki_dark_measure_1(p, nummeas, inttime, gainmode, buf, bsize)) != MUNKI_OK) {
		free(buf);
		return ev;
	}

	if ((ev = munki_dark_measure_2(p, raw, nummeas, *inttime, gainmode, buf, bsize))
	                                                                           != MUNKI_OK) {
		free(buf);
		return ev;
	}
	free(buf);

#ifdef DUMP_DARKM
	/* Dump raw dark readings to a file "mkddump.txt" */
	if (ddumpdarkm) {
		int j;
		FILE *fp;
		
		if ((fp = fopen("mkddump.txt", "a")) == NULL)
			a1logw(p->log,"Unable to open debug file mkddump.txt\n");
		else {
			fprintf(fp, "\nDark measure: nummeas %d, inttime %f, gainmode %d, darkcells %f\n",nummeas,*inttime,gainmode, raw[-1]);
			fprintf(fp,"\t\t\t{ ");
			for (j = 0; j < (m->nraw-1); j++)
				fprintf(fp, "%f, ",raw[j]);
			fprintf(fp, "%f },\n",raw[j]);
			fclose(fp);
		}
	}
#endif
	return ev;
}

/* Heat the LED up for given number of seconds by taking a reading */
munki_code munki_heatLED(
	munki *p,
	double htime		/* Heat up time */
) {
	munkiimp *m = (munkiimp *)p->m;
	double inttime = m->cal_int_time; 		/* Integration time to use/used */
	int nummeas;
	unsigned char *buf;		/* Raw USB reading buffer */
	unsigned int bsize;
	int rv;

	a1logd(p->log,3,"munki_heatLED called \n");

	nummeas = munki_comp_ru_nummeas(p, htime, inttime);

	if (nummeas <= 0)
		return MUNKI_OK;
		
	/* Allocate temporaries */
	bsize = m->nsen * 2 * nummeas;
	if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
		a1logd(p->log,1,"munki_heatLED malloc %d bytes failed (10)\n",bsize);
		return MUNKI_INT_MALLOC;
	}

	a1logd(p->log,3,"Triggering measurement cycle, nummeas %d, inttime %f\n", nummeas, inttime);

	if ((rv = munki_trigger_one_measure(p, nummeas, &inttime, 0, 1, 0))
	                                                                        != MUNKI_OK) {
		free(buf);
		return rv;
	}

	a1logd(p->log,3,"Gathering readings\n");

	rv = munki_readmeasurement(p, nummeas, 0, buf, bsize, NULL, 1, 0);

	free(buf);

	return rv;
}


/* Take a reflective or emissive white reference sens measurement, */
/* subtracts black and processes into wavelenths. */
/* (absraw is usually ->white_data) */
munki_code munki_whitemeasure(
	munki *p,
	double *absraw,			/* Return array [-1 nraw] of absraw values (may be NULL) */
	double *optscale,		/* Return scale for gain/int time to make optimal (may be NULL) */
	int nummeas,			/* Number of readings to take */
	double *inttime, 		/* Integration time to use/used */
	int gainmode,			/* Gain mode to use, 0 = normal, 1 = high */
	double targoscale		/* Ratio of optimal sensor value to aim for */
) {
	munki_code ev = MUNKI_OK;
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	unsigned char *buf;		/* Raw USB reading buffer */
	unsigned int bsize;
	int ninvmeas = 0;			/* Number of invalid measurements */
	double **multimes;		/* Multiple measurement results */
	double sensavg;			/* Overall average of sensor readings */
	double darkthresh;		/* Dark threshold */
	double trackmax[3];		/* Track optimum target & darkthresh */
	double maxval;			/* Maximum multimeas value */
	int rv;

	a1logd(p->log,3,"munki_whitemeasure called \n");

	if (s->reflective) {
		/* Compute invalid samples to allow for LED warmup */
		ninvmeas = munki_comp_ru_nummeas(p, m->refinvalidsampt, *inttime);
	}

	if (nummeas <= 0)
		return MUNKI_INT_ZEROMEASURES;
		
	/* Allocate temporaries */
	bsize = m->nsen * 2 * (ninvmeas + nummeas);
	if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
		a1logd(p->log,1,"munki_whitemeasure malloc %d bytes failed (10)\n",bsize);
		return MUNKI_INT_MALLOC;
	}

	a1logd(p->log,3,"Triggering measurement cycle, ninvmeas %d, nummeas %d, inttime %f, gainmode %d\n",
	                                              ninvmeas, nummeas, *inttime, gainmode);

	if ((ev = munki_trigger_one_measure(p, ninvmeas + nummeas, inttime, gainmode, 1, 0))
	                                                                        != MUNKI_OK) {
		free(buf);
		return ev;
	}

	a1logd(p->log,3,"Gathering readings\n");

	if ((ev = munki_readmeasurement(p, ninvmeas + nummeas, 0, buf, bsize, NULL, 1, 0))
	                                                                      != MUNKI_OK) {
		free(buf);
		return ev;
	}

  	multimes = dmatrix(0, nummeas-1, -1, m->nraw-1);

	/* Take a buffer full of raw readings, and convert them to */
	/* floating point sensor readings. Check for saturation */
	if ((rv = munki_sens_to_raw(p, multimes, NULL, buf, ninvmeas, nummeas, m->satlimit,
		                                                     &darkthresh)) != MUNKI_OK) {
		free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
		return rv;
	}

#ifdef PLOT_DEBUG
	printf("Dark data:\n");
	plot_raw(s->dark_data);
#endif

	trackmax[0] = darkthresh;		/* Track the dark threshold value */
	trackmax[1] = m->optsval;		/* Track the optimal sensor target value */
	trackmax[2] = m->satlimit;		/* For debugging */

	/* Subtract the black from sensor values and convert to */
	/* absolute (integration & gain scaled), zero offset based, */
	/* linearized sensor values. */
	/* Return the highest individual element. */
	munki_sub_raw_to_absraw(p, nummeas, *inttime, gainmode, multimes, s->dark_data,
	                             trackmax, 3, &maxval);
	darkthresh = trackmax[0];
	free(buf);

	if (absraw != NULL) {
		/* Average a set of measurements into one. */
		/* Return nz if the readings are not consistent */
		/* Return the overall average. */
		rv = munki_average_multimeas(p, absraw, multimes, nummeas, &sensavg, darkthresh);     
	
#ifndef IGNORE_WHITE_INCONS
		if (rv) {
			free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
			return MUNKI_RD_WHITEREADINCONS;
		}
#endif /* IGNORE_WHITE_INCONS */

		a1logd(p->log,3,"Average absolute sensor readings, avg %f, max %f, darkth %f satth %f\n",
		               sensavg,maxval,darkthresh,trackmax[2]);
#ifdef PLOT_DEBUG
		printf("absraw whitemeas:\n");
		plot_raw(absraw);
#endif
	}

	if (optscale != NULL) {
		double opttarget;       /* Optimal reading scale target value */

		opttarget = targoscale * trackmax[1];
		if (maxval < 0.01)		/* Could go -ve */
			maxval = 0.01;
		*optscale = opttarget/maxval; 

		a1logd(p->log,3,"Targscale %f, maxval %f, optimal target = %f, amount to scale = %f\n",
		     targoscale, maxval, opttarget, *optscale);
	}

	free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);		/* Free after using *pmax */

	return ev;
}

/* Given an absraw white reference measurement, */
/* compute the wavelength equivalents. */
/* (absraw is usually ->white_data) */
/* (abswav1 is usually ->cal_factor1) */
/* (abswav2 is usually ->cal_factor2) */
munki_code munki_compute_wav_whitemeas(
	munki *p,
	double *abswav1,		/* Return array [nwav1] of abswav values (may be NULL) */
	double *abswav2,		/* Return array [nwav2] of abswav values (if hr_init, may be NULL) */
	double *absraw			/* Given array [-1 nraw] of absraw values */
) {
	munkiimp *m = (munkiimp *)p->m;

	/* Convert an absraw array from raw wavelengths to output wavelenths */
	if (abswav1 != NULL) {
		munki_absraw_to_abswav1(p, 1, &abswav1, &absraw);

#ifdef PLOT_DEBUG
		printf("White meas converted to wavelengths std res:\n");
		plot_wav1(m, abswav1);
#endif
	}

#ifdef HIGH_RES
	if (abswav2 != NULL && m->hr_inited == 2) {
		munki_absraw_to_abswav2(p, 1, &abswav2, &absraw);

#ifdef PLOT_DEBUG
		printf("Converted to wavelengths high res:\n");
		plot_wav2(m, abswav2);
#endif
	}
#endif /* HIGH_RES */

	return MUNKI_OK;
}

/* Take a reflective white reference measurement, */
/* subtracts black and decompose into base + LED temperature components */
munki_code munki_ledtemp_whitemeasure(
	munki *p,
	double *white,			/* Return [-1 nraw] of temperature compensated white reference */
	double **iwhite,		/* Return array [-1 nraw][2] of absraw base and scale values */
	double *reftemp,		/* Return a reference temperature to normalize to */
	int nummeas,			/* Number of readings to take */
	double inttime, 		/* Integration time to use/used */
	int gainmode			/* Gain mode to use, 0 = normal, 1 = high */
) {
	munki_code ev = MUNKI_OK;
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	unsigned char *buf;		/* Raw USB reading buffer */
	unsigned int bsize;
	int ninvmeas = 0;		/* Number of invalid measurements */
	double **multimes;		/* Multiple measurement results */
	double *ledtemp;		/* LED temperature for each measurement */
	double darkthresh;		/* Dark threshold */
	int rv;

	a1logd(p->log,3,"munki_ledtemp_whitemeasure called \n");

	/* Compute invalid samples to allow for LED warmup */
	ninvmeas = munki_comp_ru_nummeas(p, m->refinvalidsampt, inttime);

	if (nummeas <= 0) {
		return MUNKI_INT_ZEROMEASURES;
	}
		
	/* Allocate temporaries */
	bsize = m->nsen * 2 * (ninvmeas + nummeas);
	if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
		a1logd(p->log,1,"munki_whitemeasure malloc %d bytes failed (10)\n",bsize);
		return MUNKI_INT_MALLOC;
	}

	a1logd(p->log,3,"Triggering measurement cycle, ninvmeas %d, nummeas %d, inttime %f, gainmode %d\n",
	                                              ninvmeas, nummeas, inttime, gainmode);

	if ((ev = munki_trigger_one_measure(p, ninvmeas + nummeas, &inttime, gainmode, 1, 0))
	                                                                        != MUNKI_OK) {
		free(buf);
		return ev;
	}

	a1logd(p->log,3,"Gathering readings\n");

	if ((ev = munki_readmeasurement(p, ninvmeas + nummeas, 0, buf, bsize, NULL, 1, 0))
	                                                                      != MUNKI_OK) {
		free(buf);
		return ev;
	}

  	multimes = dmatrix(0, nummeas-1, -1, m->nraw-1);
  	ledtemp = dvector(0, nummeas-1);

	/* Take a buffer full of raw readings, and convert them to */
	/* floating point sensor readings. Check for saturation */
	if ((ev = munki_sens_to_raw(p, multimes, ledtemp, buf, ninvmeas, nummeas, m->satlimit,
	                                                           &darkthresh)) != MUNKI_OK) {
		free(buf);
		free_dvector(ledtemp, 0, nummeas-1);
		free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
		return ev;
	}
	free(buf);

	/* Make the reference temperature nominal */
	*reftemp = 0.5 * (ledtemp[0] + ledtemp[nummeas-1]);

#ifdef PLOT_DEBUG
	printf("Ledtemp Dark data:\n");
	plot_raw(s->dark_data);
#endif

	/* Subtract the black from sensor values and convert to */
	/* absolute (integration & gain scaled), zero offset based, */
	/* linearized sensor values. */
	munki_sub_raw_to_absraw(p, nummeas, inttime, gainmode, multimes, s->dark_data,
	                             &darkthresh, 1, NULL);


	/* For each raw wavelength, compute a linear regression */
	{
		int i, w;
		double tt, ss, sx, sy, sxdss, stt, b;

		ss = (double)nummeas;
		for (sx = 0.0, i = 0; i < nummeas; i++)
			sx += ledtemp[i];
		sxdss = sx/ss;

		for (w = -1; w < m->nraw; w++) {
			for (sy = 0.0, i = 0; i < nummeas; i++)
				sy += multimes[i][w];
			
			for (stt = b = 0.0, i = 0; i < nummeas; i++) {
				tt = ledtemp[i] - sxdss;
				stt += tt * tt;
				b += tt * multimes[i][w];
			}
			b /= stt;

			iwhite[0][w] = (sy - sx * b)/ss;
			iwhite[1][w] = b;
		}
	}
#ifdef DEBUG
	{	/* Verify the linear regression */
		int i, w;
		double x, terr = 0.0, errc = 0.0;

		for (w = -1; w < m->nraw; w++) {
			for (i = 0; i < nummeas; i++) {
				x = iwhite[0][w] + ledtemp[i] * iwhite[1][w];
				terr += fabs(x -  multimes[i][w]);
				errc++;
			}
		}
		terr /= errc;
		printf("Linear regression average error = %f\n",terr);
	}
#endif

#ifdef PLOT_TEMPCOMP
	/* Plot the raw spectra and model, 3 at a time */
	{
		int i, j, k;
		double *indx;
		double **mod;
		indx = dvectorz(0, nummeas-1);  
	  	mod = dmatrix(0, 5, 0, nummeas-1);
		for (i = 0; i < nummeas; i++)
			indx[i] = 3.0 * i/(nummeas-1.0);

		for (j = 0; (j+2) < (m->nraw-1); j += 3) {
			for (i = 0; i < nummeas; i++) {
				for (k = j; k < (j + 3); k++) {
					mod[k-j][i] = iwhite[0][k] + ledtemp[i] * iwhite[1][k];
				}
				for (k = j; k < (j + 3); k++) {
					mod[k-j+3][i] = multimes[i][k];
				}
			}

			plot_msg("Bands %d - %d\n",j, j+2);
			do_plot6(indx, mod[0], mod[1], mod[2], mod[3], mod[4], mod[5], nummeas);
		}
		free_dvector(indx, 0, nummeas-1);  
	  	free_dmatrix(mod, 0, 5, 0, nummeas-1);
	}
#endif

	a1logd(p->log,3,"Computed linear regression\n");

#ifdef ENABLE_LEDTEMPC
	/* Compute a temerature compensated set of white readings */
	if ((ev = munki_ledtemp_comp(p, multimes, ledtemp, nummeas, *reftemp, iwhite)) != MUNKI_OK) {   
		free_dvector(ledtemp, 0, nummeas-1);
		free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
		return ev;
	}
#endif /* ENABLE_LEDTEMPC */

	/* Average a set of measurements into one. */
	if ((rv = munki_average_multimeas(p, white, multimes, nummeas, NULL, darkthresh)) != 0) {
		free_dvector(ledtemp, 0, nummeas-1);
		free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
		a1logd(p->log,3,"munki_ledtemp_whitemeasure: readings are inconsistent\n");
		return MUNKI_RD_DARKREADINCONS;
	}

	free_dvector(ledtemp, 0, nummeas-1);
	free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);

	return ev;
}

/* Given the ledtemp base and scale values, */
/* return an absraw reflective white reference for the */
/* given temperature */ 
munki_code munki_ledtemp_white(
	munki *p,
	double *absraw,			/* Return array [-1 nraw] of absraw base and scale values */
	double **iwhite,		/* ledtemp base and scale */
	double ledtemp			/* LED temperature value */
) {
	munkiimp *m = (munkiimp *)p->m;
	int w;

	for (w = -1; w < m->nraw; w++)
		absraw[w] = iwhite[0][w] + ledtemp * iwhite[1][w];

	return MUNKI_OK;
}

/* Given a set of absraw sensor readings and the corresponding temperature, */
/* compensate the readings to be at the nominated temperature. */
munki_code munki_ledtemp_comp(
	munki *p,
	double **absraw,		/* [nummeas][raw] measurements to compensate */
	double *ledtemp,		/* LED temperature for each measurement */
	int nummeas,			/* Number of measurements */
	double reftemp,			/* LED reference temperature to compensate to */
	double **iwhite			/* ledtemp base and scale information */
) {
	munkiimp *m = (munkiimp *)p->m;
	int i, w;

	for (i = 0; i < nummeas; i++) {
		for (w = 0; w < m->nraw; w++) {		/* Don't try and compensate shielded values */
			double targ, attemp;
			targ   = iwhite[0][w] + reftemp * iwhite[1][w];
			attemp = iwhite[0][w] + ledtemp[i] * iwhite[1][w];

			absraw[i][w] *= targ/attemp;
		}
	}
	return MUNKI_OK;
}

/* Trigger measure and gather raw readings using the current mode. */
munki_code munki_read_patches_1(
	munki *p,
	int ninvmeas,			/* Number of extra invalid measurements at start */
	int minnummeas,			/* Minimum number of measurements to take */
	int maxnummeas,			/* Maximum number of measurements to allow for */
	double *inttime, 		/* Integration time to use/used */
	int gainmode,			/* Gain mode to use, 0 = normal, 1 = high */
	int *nmeasuered,		/* Number actually measured (excluding ninvmeas) */
	unsigned char *buf,		/* Raw USB reading buffer */
	unsigned int bsize		/* Raw USB readings buffer size in bytes */
) {
	munki_code ev = MUNKI_OK;
	munkiimp *m = (munkiimp *)p->m;

	if ((ninvmeas + minnummeas) <= 0)
		return MUNKI_INT_ZEROMEASURES;

	if ((minnummeas + ninvmeas) > maxnummeas)
		maxnummeas = (minnummeas - ninvmeas);
		
	a1logd(p->log,3,"Triggering & gathering cycle, ninvmeas %d, minnummeas %d, inttime %f, gainmode %d\n",
	                                              ninvmeas, minnummeas, *inttime, gainmode);

	if ((ev = munki_trigger_one_measure(p, ninvmeas + minnummeas, inttime, gainmode, 0, 0))
	                                                                           != MUNKI_OK) {
		return ev;
	}

	if ((ev = munki_readmeasurement(p, ninvmeas + minnummeas, m->c_measmodeflags & MUNKI_MMF_SCAN,
	                                             buf, bsize, nmeasuered, 0, 0)) != MUNKI_OK) {
		return ev;
	}

	if (nmeasuered != NULL)
		*nmeasuered -= ninvmeas;	/* Correct for invalid number */

	return ev;
}

/* Given a buffer full of raw USB values, process them into */ 
/* completely processed spectral output patch readings. */
munki_code munki_read_patches_2(
	munki *p,
	double *duration,		/* Return flash duration in seconds */
	double **specrd,		/* Return array [numpatches][nwav] of spectral reading values */
	int numpatches,			/* Number of patches to return */
	double inttime, 		/* Integration time to used */
	int gainmode,			/* Gain mode useed, 0 = normal, 1 = high */
	int ninvmeas,			/* Number of extra invalid measurements at start */
	int nummeas,			/* Number of actual measurements */
	unsigned char *buf,		/* Raw USB reading buffer */
	unsigned int bsize		/* Raw USB reading buffer size */
) {
	munki_code ev = MUNKI_OK;
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	double **multimes;		/* Multiple measurement results [maxnummeas|nummeas][-1 nraw]*/
	double **absraw;		/* Linearsised absolute sensor raw values [numpatches][-1 nraw]*/
	double *ledtemp;		/* LED temperature values */
	double darkthresh;		/* Dark threshold (for consistency checking) */
	int rv = 0;

	if (duration != NULL)
		*duration = 0.0;	/* default value */

	/* Allocate temporaries */
	multimes = dmatrix(0, nummeas-1, -1, m->nraw-1);
  	ledtemp = dvector(0, nummeas-1);
	absraw = dmatrix(0, numpatches-1, -1, m->nraw-1);

	/* Take a buffer full of raw readings, and convert them to */
	/* floating point sensor readings. Check for saturation */
	if ((rv = munki_sens_to_raw(p, multimes, ledtemp, buf, ninvmeas, nummeas,
	                                         m->satlimit, &darkthresh)) != MUNKI_OK) {
		free_dvector(ledtemp, 0, nummeas-1);
		free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
		free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
		return rv;
	}

	/* Subtract the black from sensor values and convert to */
	/* absolute (integration & gain scaled), zero offset based, */
	/* linearized sensor values. */
	munki_sub_raw_to_absraw(p, nummeas, inttime, gainmode, multimes, s->dark_data,
	                             &darkthresh, 1, NULL);

#ifdef DUMP_SCANV
	/* Dump raw scan readings to a file "mkdump.txt" */
	{
		int i, j;
		FILE *fp;
		
		if ((fp = fopen("mkdump.txt", "w")) == NULL)
			a1logw(p->log,"Unable to open debug file mkdump.txt\n");
		else {
			for (i = 0; i < nummeas; i++) {
				fprintf(fp, "%d	",i);
				for (j = 0; j < m->nraw; j++) {
					fprintf(fp, "%f	",multimes[i][j]);
				}
				fprintf(fp,"\n");
			}
			fclose(fp);
		}
	}
#endif

#ifdef ENABLE_LEDTEMPC


	/* Do LED temperature compensation of absraw values */
	if (s->reflective) {

#ifdef PLOT_TEMPCOMP
		/* Plot the raw spectra, 6 at a time */
		{
			int i, j, k;
			double *indx;
			double **mod;
			indx = dvectorz(0, nummeas-1);  
		  	mod = dmatrix(0, 5, 0, nummeas-1);
			for (i = 0; i < nummeas; i++)
				indx[i] = (double)i;
	
//		for (j = 0; (j+5) < m->nraw; j += 6) {
			for (j = 50; (j+5) < 56; j += 6) {
				for (i = 0; i < nummeas; i++) {
					for (k = j; k < (j + 6); k++) {
						mod[k-j][i] = multimes[i][k];
					}
				}
	
				plot_msg("Before temp comp, bands %d - %d\n",j, j+5);
				do_plot6(indx, mod[0], mod[1], mod[2], mod[3], mod[4], mod[5], nummeas);
			}
//		}
			free_dvector(indx, 0, nummeas-1);  
		  	free_dmatrix(mod, 0, 5, 0, nummeas-1);
		}
#endif
		/* Do the LED temperature compensation */
		if ((ev = munki_ledtemp_comp(p, multimes, ledtemp, nummeas, s->reftemp, s->iwhite_data))
		                                                                            != MUNKI_OK) {
			free_dvector(ledtemp, 0, nummeas-1);
			free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
			free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
			a1logd(p->log,3,"munki_read_patches_2 ledtemp comp failed\n");
			return ev;
		} 
#ifdef PLOT_TEMPCOMP
		/* Plot the raw spectra, 6 at a time */
		{
			int i, j, k;
			double *indx;
			double **mod;
			indx = dvectorz(0, nummeas-1);  
		  	mod = dmatrix(0, 5, 0, nummeas-1);
			for (i = 0; i < nummeas; i++)
				indx[i] = (double)i;
	
//			for (j = 0; (j+5) < m->nraw; j += 6) {
			for (j = 50; (j+5) < 56; j += 6) {
				for (i = 0; i < nummeas; i++) {
					for (k = j; k < (j + 6); k++) {
						mod[k-j][i] = multimes[i][k];
					}
				}
	
				plot_msg("After temp comp, bands %d - %d\n",j, j+5);
				do_plot6(indx, mod[0], mod[1], mod[2], mod[3], mod[4], mod[5], nummeas);
			}
//			}
			free_dvector(indx, 0, nummeas-1);  
		  	free_dmatrix(mod, 0, 5, 0, nummeas-1);
		}
#endif
	}
#endif /* ENABLE_LEDTEMPC */


	if (!s->scan) {
		if (numpatches != 1) {
			free_dvector(ledtemp, 0, nummeas-1);
			free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
			free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
			a1logd(p->log,3,"munki_read_patches_2 spot read failed because numpatches != 1\n");
			return MUNKI_INT_WRONGPATCHES;
		}

		/* Average a set of measurements into one. */
		/* Return zero if readings are consistent. */
		/* Return nz if the readings are not consistent */
		/* Return the overall average. */
		rv = munki_average_multimeas(p, absraw[0], multimes, nummeas, NULL, darkthresh);     
	} else {

		if (s->flash) {

			if (numpatches != 1) {
				free_dvector(ledtemp, 0, nummeas-1);
				free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
				free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
				a1logd(p->log,3,"munki_read_patches_2 spot read failed because numpatches != 1\n");
				return MUNKI_INT_WRONGPATCHES;
			}

#if defined(PATREC_DEBUG) && defined(PATREC_LOG)
			stop_plot_log(); 
			start_plot_log("patchrec.plot");
			g_log->debug = PATREC_LOG_LEVEL;
			g_log->logd_cc = plot_msg_fmt;
#endif
			ev = munki_extract_patches_flash(p, &rv, duration, absraw[0], multimes,
			                                                      nummeas, inttime);
#if defined(PATREC_DEBUG) && defined(PATREC_LOG)
			stop_plot_log(); 
			g_log->logd_cc = NULL;
#endif
			if (ev != MUNKI_OK) {
				free_dvector(ledtemp, 0, nummeas-1);
				free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
				free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
				a1logd(p->log,3,"munki_read_patches_2 spot read failed at munki_extract_patches_flash\n");
				return ev;
			}

		} else {
			a1logd(p->log,3,"Number of patches to be measured = %d\n",nummeas);

#if defined(PATREC_DEBUG) && defined(PATREC_LOG)
			stop_plot_log(); 
			start_plot_log("patchrec.plot");
			g_log->debug = PATREC_LOG_LEVEL;
			g_log->logd_cc = plot_msg_fmt;
#endif
			/* Recognise the required number of ref/trans patch locations, */
			/* and average the measurements within each patch. */
			ev = munki_extract_patches_multimeas(p, &rv, absraw, numpatches, multimes,
			                                                         nummeas, inttime);
#if defined(PATREC_DEBUG) && defined(PATREC_LOG)
			stop_plot_log(); 
			g_log->logd_cc = NULL;
#endif
			if (ev != MUNKI_OK) {
				free_dvector(ledtemp, 0, nummeas-1);
				free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
				free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
				a1logd(p->log,3,"munki_read_patches_2 spot read failed at munki_extract_patches_multimeas\n");
				return ev;
			}
		}
	}
	free_dvector(ledtemp, 0, nummeas-1);
	free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);

	if (rv) {
		free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
		a1logd(p->log,3,"munki_read_patches_2 spot read failed with inconsistent readings\n");
		return MUNKI_RD_READINCONS;
	}

#ifdef ENABLE_REFSTRAYC	/* Enable Reflective stray light compensation */
	if (s->reflective) {
		int i, j;
# if defined(PLOT_DEBUG) || defined(DUMP_BKLED)
		double xx[140];
		double yy[3][140];
# endif

		double fact = REFSTRAYC_FACTOR;		/* Slightly conservative */

		for (i = 0; i < numpatches; i++) {

			for (j = 0; j < m->nraw; j++) {
# if defined(PLOT_DEBUG) || defined(DUMP_BKLED)
				yy[0][j] = absraw[i][j];
# endif
				absraw[i][j] -= fact * s->white_data[j];
# if defined(PLOT_DEBUG) || defined(DUMP_BKLED)
				xx[j] = j;
				yy[1][j] = fact * s->white_data[j];
				yy[2][j] = absraw[i][j];
# endif
			}
# ifdef PLOT_DEBUG
			plot_msg("Before/After subtracting stray ref. light %d:\n",i);
			do_plot6(xx, yy[0], yy[1], yy[2], NULL, NULL, NULL, m->nraw);
# endif
# ifdef DUMP_BKLED		/* Save REFSTRAYC & REFLEDNOISE comp plot to "refbk1.txt" & "refbk2.txt" */
		{
			xspect sp[3];
			for (i = 0; i < 3; i++) {
				sp[i].spec_n = 128;
				sp[i].spec_wl_short = 0.0;
				sp[i].spec_wl_long = 127.0;
				sp[i].norm = 1.0;
				for (j = 0; j < 128; j++)
					sp[i].spec[j] = yy[i][j];
			}
			write_nxspect("refbk2.txt", sp, 3, 0);
# pragma message("######### munki DUMP_BKLED enabled! ########")
		}
# endif	/* DUMP_BKLED */
		}
	}
#endif /* ENABLE_REFSTRAYC */

	/* Convert an absraw array from raw wavelengths to output wavelenths */
	munki_absraw_to_abswav(p, numpatches, specrd, absraw);
	free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);

#ifdef APPEND_MEAN_EMMIS_VAL
	/* Append averaged emission reading to file "mkdump.txt" */
	{
		int i, j;
		FILE *fp;
		
		/* Create wavelength label */
		if ((fp = fopen("mkdump.txt", "r")) == NULL) {
			if ((fp = fopen("mkdump.txt", "w")) == NULL)
				a1logw(p->log,"Unable to reate debug file mkdump.txt\n");
			else {
				for (j = 0; j < m->nwav; j++)
					fprintf(fp,"%f ",XSPECT_WL(m->wl_short, m->wl_long, m->nwav, j));
				fprintf(fp,"\n");
				fclose(fp);
			}
		}
		if ((fp = fopen("mkdump.txt", "a")) == NULL)
			a1logw(p->log,"Unable to open debug file mkdump.txt\n");
		else {
			for (j = 0; j < m->nwav; j++)
				fprintf(fp, "%f	",specrd[0][j] * m->emis_coef[j]);
			fprintf(fp,"\n");
			fclose(fp);
		}
	}
#endif

#ifdef PLOT_DEBUG
	printf("Converted to wavelengths:\n");
	plot_wav(m, specrd[0]);
#endif

	/* Scale to the calibrated output values */
	munki_scale_specrd(p, specrd, numpatches, specrd);

#ifdef PLOT_DEBUG
	printf("Calibrated measuerment spectra:\n");
	plot_wav(m, specrd[0]);
#endif

	return ev;
}

/* Given a buffer full of raw USB values, process them into */ 
/* completely processed spectral output readings, */
/* but don't average together or extract patches or flash. */
/* This is used for delay & refresh rate measurement. */
/* !! Doesn't do LED temperature compensation for reflective !! */
/* (! Note that we aren't currently detecting saturation here!) */
munki_code munki_read_patches_2a(
	munki *p,
	double **specrd,		/* Return array [numpatches][nwav] of spectral reading values */
	int numpatches,			/* Number of patches measured and to return */
	double inttime, 		/* Integration time to used */
	int gainmode,			/* Gain mode useed, 0 = normal, 1 = high */
	unsigned char *buf,		/* Raw USB reading buffer */
	unsigned int bsize
) {
	munki_code ev = MUNKI_OK;
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	double **absraw;		/* Linearsised absolute sensor raw values [numpatches][-1 nraw]*/
	double *ledtemp;		/* LED temperature values */
	double satthresh;		/* Saturation threshold */
	double darkthresh;		/* Dark threshold for consistency scaling limit */

	/* Allocate temporaries */
	absraw = dmatrix(0, numpatches-1, -1, m->nraw-1);
	ledtemp = dvector(0, numpatches-1);

	/* Take a buffer full of raw readings, and convert them to */
	/* floating point sensor readings. Check for saturation */
	if ((ev = munki_sens_to_raw(p, absraw, ledtemp, buf, 0, numpatches,
					                        m->satlimit, &darkthresh)) != MUNKI_OK) {
		free_dvector(ledtemp, 0, numpatches-1);
		free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);
		return ev;
	}
	
	/* Subtract the black from sensor values and convert to */
	/* absolute (integration & gain scaled), zero offset based, */
	/* linearized sensor values. */
	munki_sub_raw_to_absraw(p, numpatches, inttime, gainmode, absraw, s->dark_data,
                           &darkthresh, 1, NULL);

	a1logd(p->log,3,"Number of patches measured = %d\n",numpatches);

	/* Convert an absraw array from raw wavelengths to output wavelenths */
	munki_absraw_to_abswav(p, numpatches, specrd, absraw);

	free_dvector(ledtemp, 0, numpatches-1);
	free_dmatrix(absraw, 0, numpatches-1, -1, m->nraw-1);

#ifdef PLOT_DEBUG
	printf("Converted to wavelengths:\n");
	plot_wav(m, specrd[0]);
#endif

	/* Scale to the calibrated output values */
	munki_scale_specrd(p, specrd, numpatches, specrd);

#ifdef PLOT_DEBUG
	printf("Calibrated measuerment spectra:\n");
	plot_wav(m, specrd[0]);
#endif

	return ev;
}

/* Take a measurement reading using the current mode (combined parts 1 & 2a) */
/* Converts to completely processed output readings, without averaging or extracting */
/* sample patches, for emissive measurement mode. */
/* This is used for delay & refresh rate measurement. */
munki_code munki_read_patches_all(
	munki *p,
	double **specrd,		/* Return array [numpatches][nwav] of spectral reading values */
	int numpatches,			/* Number of sample to measure */
	double *inttime, 		/* Integration time to use/used */
	int gainmode			/* Gain mode to use, 0 = normal, 1 = high */
) {
	munki_code ev = MUNKI_OK;
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	unsigned char *buf;		/* Raw USB reading buffer */
	unsigned int bsize;
	int rv = 0;

	bsize = m->nsen * 2 * numpatches;
	if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
		a1logd(p->log,1,"munki_read_patches malloc %d bytes failed (11)\n",bsize);
		return MUNKI_INT_MALLOC;
	}

	/* Trigger measure and gather raw readings */
	if ((ev = munki_read_patches_1(p, 0, numpatches, numpatches, inttime, gainmode,
	                                       NULL, buf, bsize)) != MUNKI_OK) { 
		free(buf);
		return ev;
	}

	/* Process the raw readings without averaging or extraction */
	if ((ev = munki_read_patches_2a(p, specrd, numpatches, *inttime, gainmode,
	                                                 buf, bsize)) != MUNKI_OK) {
		free(buf);
		return ev;
	}
	free(buf);
	return ev;
}

/* Take a trial emission measurement reading using the current mode. */
/* Used to determine if sensor is saturated, or not optimal */
/* in adaptive emission mode. */
munki_code munki_trialmeasure(
	munki *p,
	int *saturated,			/* Return nz if sensor is saturated */
	double *optscale,		/* Return scale for gain/int time to make optimal (may be NULL) */
	int nummeas,			/* Number of readings to take */
	double *inttime, 		/* Integration time to use/used */
	int gainmode,			/* Gain mode to use, 0 = normal, 1 = high */
	double targoscale		/* Ratio of optimal sensor value to aim for */
) {
	munki_code ev = MUNKI_OK;
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	unsigned char *buf;		/* Raw USB reading buffer */
	unsigned int bsize;
	double **multimes;		/* Multiple measurement results */
	double *absraw;		/* Linearsised absolute sensor raw values */
	int nmeasuered;			/* Number actually measured */
	double sensavg;			/* Overall average of sensor readings */
	double darkthresh;		/* Dark threshold */
	double trackmax[2];		/* Track optimum target */
	double maxval;			/* Maximum multimeas value */
	int rv;

	if (s->reflective) {
		a1logw(p->log, "munki_trialmeasure: Assert - not meant to be used for reflective read!\n");
		return MUNKI_INT_ASSERT;
	}
	
	if (nummeas <= 0)
		return MUNKI_INT_ZEROMEASURES;
		
	/* Allocate up front to avoid delay between trigger and read */
	bsize = m->nsen * 2 * nummeas;
	if ((buf = (unsigned char *)malloc(sizeof(unsigned char) * bsize)) == NULL) {
		a1logd(p->log,1,"munki_trialmeasure malloc %d bytes failed (12)\n",bsize);
		return MUNKI_INT_MALLOC;
	}
	multimes = dmatrix(0, nummeas-1, -1, m->nraw-1);
	absraw = dvector(-1, m->nraw-1);

	a1logd(p->log,3,"Triggering measurement cycle, nummeas %d, inttime %f, gainmode %d\n",
	                                              nummeas, *inttime, gainmode);

	if ((ev = munki_trigger_one_measure(p, nummeas, inttime, gainmode, 1, 0)) != MUNKI_OK) {
		free_dvector(absraw, -1, m->nraw-1);
		free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
		free(buf);
		return ev;
	}

	a1logd(p->log,3,"Gathering readings\n");
	if ((ev = munki_readmeasurement(p, nummeas, m->c_measmodeflags & MUNKI_MMF_SCAN,
	                                             buf, bsize, &nmeasuered, 1, 0)) != MUNKI_OK) {
		free_dvector(absraw, -1, m->nraw-1);
		free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
		free(buf);
		return ev;
	}

	if (saturated != NULL)			/* Initialize return flag */
		*saturated = 0;

	/* Take a buffer full of raw readings, and convert them to */
	/* floating point sensor readings. Check for saturation */
	if ((rv = munki_sens_to_raw(p, multimes, NULL, buf, 0, nmeasuered, m->satlimit,
		                                                 &darkthresh)) != MUNKI_OK) {
	 	if (rv != MUNKI_RD_SENSORSATURATED) {
			free_dvector(absraw, -1, m->nraw-1);
			free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
			free(buf);
			return rv;
		}
	 	if (saturated != NULL)
			*saturated = 1;
	}
	free(buf);

	/* Comute dark subtraction for this trial's parameters */
	if ((ev = munki_interp_dark(p, s->dark_data, *inttime, gainmode)) != MUNKI_OK) {
		free_dvector(absraw, -1, m->nraw-1);
		free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);
		a1logd(p->log,3,"munki_imp_measure interplate dark ref failed\n");
		return ev;
	}

	trackmax[0] = darkthresh;		/* Track dark threshold */
	trackmax[1] = m->optsval;		/* Track the optimal sensor target value */

	/* Subtract the black from sensor values and convert to */
	/* absolute (integration & gain scaled), zero offset based, */
	/* linearized sensor values. */
	/* Return the highest individual element. */
	munki_sub_raw_to_absraw(p, nmeasuered, *inttime, gainmode, multimes, s->dark_data,
	                             trackmax, 2, &maxval);
	darkthresh = trackmax[0];

	/* Average a set of measurements into one. */
	/* Return nz if the readings are not consistent */
	/* Return the overall average. */
	rv = munki_average_multimeas(p, absraw, multimes, nmeasuered, &sensavg, darkthresh);     

	/* Ignore iconsistent readings ?? */

#ifdef PLOT_DEBUG
	printf("Average absolute sensor readings, average = %f, max %f\n",sensavg,maxval);
	plot_raw(absraw);
#endif

	if (optscale != NULL) {
		double opttarget;		/* Optimal sensor target */

		opttarget = targoscale * trackmax[1];
		if (maxval < 0.01)		/* Could go -ve */
			maxval = 0.01;
		*optscale = opttarget/ maxval; 
		a1logd(p->log,4,"Targscale %f, maxval %f, optimal target = %f, amount to scale = %f\n",
		     targoscale, maxval, opttarget, *optscale);
	}

	free_dvector(absraw, -1, m->nraw-1);
	free_dmatrix(multimes, 0, nummeas-1, -1, m->nraw-1);		/* Free after using *pmax */

	return ev;
}

/* Trigger a single measurement cycle. This could be a dark calibration, */
/* a calibration, or a real measurement. This is used to create the */
/* higher level "calibrate" and "take reading" functions. */
/* The setup for the operation is in the current mode state. */
/* Call munki_readmeasurement() to collect the results */
munki_code
munki_trigger_one_measure(
	munki *p,
	int nummeas,			/* Minimum number of measurements to make */
	double *inttime, 		/* Integration time to use/used */
	int gainmode,			/* Gain mode to use, 0 = normal, 1 = high */
	int calib_measure,		/* flag - nz if this is a calibration measurement */
	int dark_measure		/* flag - nz if this is a dark measurement */
) {
	munki_code ev = MUNKI_OK;
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	double dintclocks;
	int intclocks;		/* Number of integration clocks */
	int measmodeflags;	/* Measurement mode command flags */
	int holdtempduty;	/* Hold temperature duty cycle */

	/* Compute integration clocks. Take account of (seeming) dead integration time */
	dintclocks = floor((*inttime)/m->intclkp + 0.5);
	intclocks = (int)dintclocks;
	*inttime = (double)intclocks * m->intclkp;		/* Quantized integration time */

	/* Create measurement mode flag byte for this operation */
	measmodeflags = 0;
	if (s->scan && !calib_measure)
		measmodeflags |= MUNKI_MMF_SCAN;		/* Never scan on a calibration */
	if (s->reflective && !dark_measure)
		measmodeflags |= MUNKI_MMF_LAMP;		/* Need lamp if reflective and not dark measure */
	if (gainmode == 1)
		measmodeflags |= MUNKI_MMF_HIGHGAIN;	/* High gain mode */
	holdtempduty = m->ledholdtempdc;			/* From the EEProm value */

	/* Trigger a measurement */
	if ((ev = munki_triggermeasure(p, intclocks, nummeas, measmodeflags, holdtempduty)) != MUNKI_OK)
		return ev;
	
	m->c_measmodeflags = measmodeflags;

	m->c_inttime = *inttime;

	return ev;
}

/* ============================================================ */
/* lower level reading processing and computation */

/* Take a buffer full of raw readings, and convert them to */
/* directly to floating point raw values. Return MUNKI_RD_SENSORSATURATED if any is saturated */
/* (No black subtraction or linearization is performed) */
munki_code munki_sens_to_raw(
	munki *p,
	double **raw,			/* Array of [nummeas-ninvalid][-1 nraw] value to return */
	double *ledtemp,		/* Optional array [nummeas-ninvalid] LED temperature values to return */
	unsigned char *buf,		/* Raw measurement data must be 274 * nummeas */
	int ninvalid,			/* Number of initial invalid readings to skip */
	int nummeas,			/* Number of readings measured */
	double satthresh,		/* Saturation threshold to trigger error in raw units (if > 0.0) */
	double *pdarkthresh		/* Return a dark threshold value = shielded cell values */
) {
	munkiimp *m = (munkiimp *)p->m;
	int i, j, k;
	unsigned char *bp;
	double maxval = -1e38;
	double darkthresh = 0.0;
	double ndarkthresh = 0.0;
	int sskip = 2 * 6;		/* Bytes to skip at start */
	int eskip = 2 * 3;		/* Bytes to skip at end */

	if ((m->nraw * 2 + sskip + eskip) != (m->nsen * 2)) {
		a1logw(p->log,"NRAW %d and NRAWB %d don't match!\n",m->nraw,m->nsen * 2);
		return MUNKI_INT_ASSERT;
	}

	if (ninvalid > 0) 
		a1logd(p->log, 4, "munki_sens_to_raw: Skipping %d invalid readings\n",ninvalid);

	/* Now process the buffer values */
	for (bp = buf + ninvalid * m->nsen * 2, i = 0; i < nummeas; i++, bp += eskip) {

		/* The first 4 readings are shielded cells, and we use them as an */								/* estimate of the dark reading consistency, as well as for */
		/* compensating the dark level calibration for any temperature changes. */

		/* raw average of all measurement shielded cell values */
		for (k = 0; k < 4; k++) {
			darkthresh += (double)buf2ushort(bp + k * 2);
			ndarkthresh++;
		}

		/* raw of shielded cells per reading */
		raw[i][-1] = 0.0;
		for (k = 0; k < 4; k++) {
			raw[i][-1] += (double)buf2ushort(bp + k * 2);
		}
		raw[i][-1] /= 4.0;
		
		/* The LED voltage drop is the last 16 bits in each reading */
		if (ledtemp != NULL)
			ledtemp[i] = (double)buf2ushort(bp + (m->nsen * 2) - 2);

		/* The 128 raw spectral values */
		for (bp += sskip, j = 0; j < m->nraw; j++, bp += 2) {
			unsigned int rval;
			double fval;

			rval = buf2ushort(bp);
			fval = (double)rval;
			raw[i][j] = fval;
//			printf("~1 i = %d, j = %d, addr % 274 = %d, val = %f\n",i,j,(bp - buf) % 274, fval); 

			if (fval > maxval)
				maxval = fval;
		}
	}

	/* Check if any are over saturation threshold */
	if (satthresh > 0.0) {
		if (maxval > satthresh) {
			a1logd(p->log,4,"munki_sens_to_raw: Max sens %f > satthresh %f\n",maxval,satthresh);
			return MUNKI_RD_SENSORSATURATED;
		}
		a1logd(p->log,4,"munki_sens_to_raw: Max sens %f < satthresh %f\n",maxval,satthresh);
	}

	darkthresh /= ndarkthresh;
	if (pdarkthresh != NULL)
		*pdarkthresh = darkthresh;
	a1logd(p->log,3,"munki_sens_to_raw: Dark thrheshold = %f\n",darkthresh);

	return MUNKI_OK;
}

/* Subtract the black from raw values and convert to */
/* absolute (integration & gain scaled), zero offset based, */
/* linearized raw values. */
/* Return the highest individual element. */
void munki_sub_raw_to_absraw(
	munki *p,
	int nummeas,			/* Return number of readings measured */
	double inttime, 		/* Integration time used */
	int gainmode,			/* Gain mode, 0 = normal, 1 = high */
	double **absraw,		/* Source/Desination array [-1 nraw] */
	double *sub,			/* Value to subtract [-1 nraw] (ie. cal dark data) */
	double *trackmax, 		/* absraw values that should be offset the same as max */
	int ntrackmax,			/* Number of trackmax values */
	double *maxv			/* If not NULL, return the maximum value */
) {
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	int npoly;			/* Number of linearisation coefficients */
	double *polys;		/* the coeficients */
	double scale;		/* Absolute scale value */
	double submax = -1e6;	/* Subtraction value maximum */
	double asub[NSEN_MAX];
	double avgscell, zero;
	double rawmax, maxval = -1e38;
	double maxzero = 0.0;
	int i, j, k;

	/* Heusristic correction for LED interference bump for 0.018 secs int_time */
	int    pos[] = { 0,    20,   56,   62,   75,   127 };
//	double off[] = { 0.7, 0.0,  0.6, -0.9,  -1.2, -0.7 };
	double off[] = { 0.7, 0.0,  0.6, -0.9,  -0.8, -0.5 };
	
	if (gainmode) {				/* High gain */
		npoly = m->nlin1;		/* Encodes gain too */
		polys = m->lin1;
	} else {					/* Low gain */
		npoly = m->nlin0;
		polys = m->lin0;
	}
	scale = 1.0/inttime;

	/* Adjust black to allow for temperature change by using the */
	/* shielded cell values as a reference. */
	/* We use a heuristic to compute a zero based scale for adjusting the */
	/* black. It's not clear why it works best this way, or how */
	/* dependent on the particular instrument the magic numbers are, */
	/* but it reduces the black level error from over 10% to about 0.3% */

	/* Locate largest of black */
	for (j = 0; j < m->nraw; j++) {
		if (sub[j] > submax)
			submax = sub[j];
	}

	/* Average the shielded cell value of all the readings */
	avgscell = 0.0;
	for (i = 0; i < nummeas; i++)
		avgscell += absraw[i][-1];
	avgscell /= (double)nummeas;

	/* Compute scaling zero */
	zero = 1.08 * 0.5 * (avgscell + sub[-1]);

	/* make sure that the zero point is above any black value */
	if (zero < (1.005 * avgscell))
		zero = 1.005 * avgscell;
	if (zero < (1.005 * sub[-1]))
		zero = 1.005 * sub[-1];
	if (zero < (1.005 * submax))
		zero = 1.005 * submax;

	a1logd(p->log,4,"Black shielded value = %f, Reading shielded value = %f\n",sub[-1], avgscell);

	/* Compute the adjusted black for each band */
	if (s->reflective) {

		/* It seems that having the LED on shifts the shielded cell values */
		/* by about 2.5, and this stuffs up the reflective measurement. */
		/* This seems to be from the LED PWM driver, which perhaps */
		/* is synchronous to the sensor clock, and so switches */
		/* at a certain point in the transfer of data from the sensor. */
		/* The result is a step up from 0-60, and then down from 61-128. */
		/* Perhaps altering the LED PWM setting and seeing if this point */
		/* shifts would be a way of confirming this ? */
		/* There is also some stray light reflected into the sensor */
		/* from the LED, but due to the LED step, the sensor reading is */
		/* less than the dark data at some wavelengths. */
		/* The details of the LED step seem to be integration time dependent, */
		/* but decresing the scanning rate therebye increasing integration */
		/* time and light level reduces the impact of this error. */

		/* Since we do an on the fly black measurement before each */
		/* reflective measurement, ignoring the shielded cell values */
		/* shouldn't affect accuracy so much. */

#ifdef ENABLE_REFLEDINTER
		/* A heuristic to correct for the LED noise. */
		/* This is only valid for int_time of 0.0182 secs, */
		/* and it's not clear how well it works across different */
		/* temperatures or examples of the ColorMunki. */
		/* in another revision ?? */
		for (j = 0; j < m->nraw; j++) {
			int ix;
			double bl, val;

			for (ix = 0; ; ix++) {
				if (j >= pos[ix] && j <= pos[ix+1]) 
					break;
			}
			bl = (j - pos[ix])/((double)pos[ix+1] - pos[ix]);
			val = (1.0 - bl) * off[ix] + bl * off[ix+1];
			asub[j] = sub[j] + val;
		}
#else
		for (j = 0; j < m->nraw; j++)
			asub[j] = sub[j];		/* Just use the calibration dark data */
#endif

	} else {
		/* No LED on operation - use sheilded cell values */
		for (j = 0; j < m->nraw; j++) {
#ifdef ENABLE_BKDRIFTC

# ifdef HEURISTIC_BKDRIFTC
			/* heuristic scaled correction */
			asub[j] = zero - (zero - sub[j]) * (zero - avgscell)/(zero - sub[-1]);
# else
			/* simple additive correction */
#  pragma message("######### munki Simple shielded cell temperature correction! ########")
			asub[j] = sub[j] + (avgscell - sub[-1]);
# endif
#else
#  pragma message("######### munki No shielded cell temperature correction! ########")
			asub[j] = sub[j];		/* Just use the calibration dark data */
#endif
		}
	}

#if defined(PLOT_DEBUG) || defined(DUMP_BKLED)
	{
	double xx[130];
	double yy[3][130];

	for (j = -1; j < m->nraw+1; j++)
		yy[0][j+1] = 0.0;

	for (i = 0; i < nummeas; i++) {
		for (j = -1; j < m->nraw; j++)
			yy[0][j+1] += absraw[i][j];
	}
	for (j = -1; j < m->nraw; j++)
		yy[0][j+1] /= (double)nummeas;

	for (j = -1; j < m->nraw; j++)
		yy[1][j+1]= sub[j];

	/* Show what ENABLE_REFLEDINTER would do */
	for (j = 0; j < m->nraw; j++) {
		int ix;
		double bl, val;

		for (ix = 0; ; ix++) {
			if (j >= pos[ix] && j <= pos[ix+1]) 
				break;
		}
		bl = (j - pos[ix])/((double)pos[ix+1] - pos[ix]);
		val = (1.0 - bl) * off[ix] + bl * off[ix+1];
		yy[2][j+1] = yy[0][j+1] - val;
	}
	yy[2][0] = yy[0][0];

	for (j = -1; j < m->nraw; j++)
		xx[j+1] = (double)j;

	xx[0]= -10.0;

# ifdef PLOT_DEBUG
	plot_msg("sub_raw_to_absraw %d samp avg - dark ref:\n",nummeas);
	do_plot(xx, yy[0], yy[1], yy[2], 129);
# endif
# ifdef DUMP_BKLED
	{
		xspect sp[3];
		for (i = 0; i < 3; i++) {
			sp[i].spec_n = 128;
			sp[i].spec_wl_short = 0.0;
			sp[i].spec_wl_long = 127.0;
			sp[i].norm = 1.0;
			for (j = 0; j < 128; j++)
				sp[i].spec[j] = yy[i][j+1];
		}
		write_nxspect("refbk1.txt", sp, 3, 0);
	}
# endif	/* DUMP_BKLED */
	}
#endif	/* PLOT_DEBUG || DUMP_BKLED */

	/* For each measurement */
	for (i = 0; i < nummeas; i++) {
		double rval, sval, lval;
	
		for (j = 0; j < m->nraw; j++) {

			rval = absraw[i][j];

			sval = rval - asub[j];		/* Make zero based */

#ifdef ENABLE_NONLINCOR	
			/* Linearise */
			for (lval = polys[npoly-1], k = npoly-2; k >= 0; k--)
				lval = lval * sval + polys[k];
#else
			lval = sval;
#endif
			lval *= scale;
			absraw[i][j] = lval;

			/* Track the maximum value and the black that was subtracted from it */ 
			if (lval > maxval) {
				maxval = lval;
				rawmax = rval;
				maxzero = asub[j];
				if (maxv != NULL)
					*maxv = absraw[i][j];
			}
		}
	}

	/* Process the "tracked to max" values too */
	if (ntrackmax > 0 && trackmax != NULL)  {
		for (i = 0; i < ntrackmax; i++) {
			double rval, fval, lval;

			rval = trackmax[i];
			fval = rval - maxzero;

#ifdef ENABLE_NONLINCOR	
			/* Linearise */
			for (lval = polys[npoly-1], k = npoly-2; k >= 0; k--)
				lval = lval * fval + polys[k];
#else
			lval = fval;
#endif
			lval *= scale;
			trackmax[i] = lval;
//			printf("~1 trackmax[%d] = %f, maxzero = %f\n",i,lval,maxzero);
		}
	}
}

/* Average a set of sens or absens measurements into one. */
/* (Make sure darkthresh is tracked if absens is being averaged!) */
/* Return zero if readings are consistent and not saturated. */
/* Return nz if the readings are not consistent */
/* Return the overall average. */
int munki_average_multimeas(
	munki *p,
	double *avg,			/* return average [-1 nraw] */
	double **multimeas,		/* Array of [nummeas][-1 nraw] value to average */
	int nummeas,			/* number of readings to be averaged */
	double *poallavg,		/* If not NULL, return overall average of bands and measurements */
	double darkthresh		/* Dark threshold (used for consistency check scaling) */
) {
	munkiimp *m = (munkiimp *)p->m;
	int i, j;
	double oallavg = 0.0;
	double maxavg = -1e38;		/* Track min and max averages of readings */
	double minavg = 1e38;
	double norm;
	int rv = 0;
	
	a1logd(p->log,3,"munki_average_multimeas %d readings (darkthresh %f)\n",nummeas,darkthresh);

	for (j = -1; j < m->nraw; j++) 
		avg[j] = 0.0;

	/* Now process the buffer values */
	for (i = 0; i < nummeas; i++) {
		double measavg = 0.0;

		avg[-1] += multimeas[i][-1];			/* shielded cell value */

		for (j = 0; j < m->nraw; j++) {
			double val;

			val = multimeas[i][j];

			measavg += val;
			avg[j] += val;
		}
		measavg /= (double)m->nraw;
		oallavg += measavg;
		if (measavg < minavg)
			minavg = measavg;
		if (measavg > maxavg)
			maxavg = measavg;
	}

	for (j = -1; j < m->nraw; j++) 
		avg[j] /= (double)nummeas;
	oallavg /= (double)nummeas;

	if (poallavg != NULL)
		*poallavg = oallavg;

	norm = fabs(0.5 * (maxavg+minavg));
	darkthresh = fabs(darkthresh);
	if (darkthresh < DARKTHSCAMIN)
		darkthresh = DARKTHSCAMIN;
	a1logd(p->log,3,"norm = %f, dark thresh = %f\n",norm,darkthresh);
	if (norm < (2.0 * darkthresh))
		norm = 2.0 * darkthresh;

	a1logd(p->log,4,"avg_multi: overall avg = %f, minavg = %f, maxavg = %f, variance %f, THR %f (darkth %f)\n",
	                   oallavg,minavg,maxavg,(maxavg - minavg)/norm, PATCH_CONS_THR,darkthresh);
	if ((maxavg - minavg)/norm > PATCH_CONS_THR) {
		rv |= 1;
	}
	return rv;
}

/* Minimum number of scan samples in a patch */
#define MIN_SAMPLES 3

/* Number of window size tries to use */
#define WIN_TRIES 20

/* Range of bands to detect transitions */
#define BL 30	/* Start */
#define BH 101	/* End */
#define NFB 7	/* [7] Number of filtered bands (must be odd and < 10) */

/* Record of possible patch within a reading buffer */
typedef struct {
	int ss;				/* Start sample index */
	int no;				/* Number of samples */
	double nno;			/* Speed normalized number of samples */
	int use;			/* nz if patch is to be used */
} munki_patch;

/* Recognise the required number of ref/trans patch locations, */
/* and average the measurements within each patch. */
/* *flags returns zero if readings are consistent. */
/* *flags returns nz if the readings are not consistent */
/* (Doesn't extract [-1] shielded values, since they have already been used) */
munki_code munki_extract_patches_multimeas(
	munki *p,
	int *flags,				/* return flags */
	double **pavg,			/* return patch average [naptch][-1 nraw] */
	int tnpatch,			/* Target number of patches to recognise */
	double **multimeas,		/* Array of [nummeas][-1 nraw] value to extract from */
	int nummeas,			/* number of readings made */
	double inttime			/* Integration time (used to adjust consistency threshold) */
) {
	munkiimp *m = (munkiimp *)p->m;
	int i, j, k, pix;
	int fbands[NFB][2];			/* Start & end+1 raw indexes of filter bands */
	double **fraw;				/* NFB filtered raw bands */
	double *slope;				/* Accumulated absolute difference between i and i+1 */
	double *fslope;				/* Filtered slope */
	munki_patch *pat;			/* Possible patch information */
	int npat, apat = 0, fpat;	/* Number of potential, number allocated, number found */
	double *maxval;				/* Maximum input value for each wavelength */
	double fmaxslope = 0.0;
	double maxslope = 0.0;
	double minslope =  1e38;
	double thresh = 0.4;		/* Slope threshold */
	rspl *pcurve = NULL;		/* Patch width fitting curve */
	int try;					/* Thresholding try */
	double avglength;			/* Average length of patches */
	double maxlength;			/* Max length of of patches */
	int *sizepop;				/* Size popularity of potential patches */
	int msthr;					/* Median search threshold */
	double median;				/* median potential patch width */
	double window;				/* +/- around median to accept */
	double max_window;			/* window stopping point */
	double lead_avg, trail_avg;	/* Average of leader and trailer */
	double white_avg;			/* Average of (aproximate) white data */
	int rv = 0;
	double patch_cons_thr = PATCH_CONS_THR * m->scan_toll_ratio;
#ifdef PATREC_DEBUG
	double **plot;
	double *pplot[10];
#endif

	a1logd(p->log,3,"munki_extract_patches_multimeas: looking for %d patches out of %d samples\n",tnpatch,nummeas);

	maxval = dvectorz(-1, m->nraw-1);  

	/* Loosen consistency threshold for short intergation time */
	if (inttime < 0.012308)		/* Smaller than Rev A minimum int. time */
		patch_cons_thr *= sqrt(0.012308/inttime);

	/* Discover the maximum input value for normalisation */
	for (j = 0; j < m->nraw; j ++) {
		for (i = 0; i < nummeas; i++) {
			if (multimeas[i][j] > maxval[j])
				maxval[j] = multimeas[i][j];
		}
		if (maxval[j] < 1.0)
			maxval[j] = 1.0;
	}

#ifdef PATREC_DEBUG
	plot = dmatrixz(0, 10, 0, nummeas-1);			/* Up to 11 values */  
#ifdef PATREC_PLOT_ALLBANDS
	for (j = 0; j < (m->nraw-10); j += 10) 			/* Plot all the bands */
#else
	for (j = 24; j < (111-10); j += 30) 			/* Do some of the bands */
#endif
	{
		for (k = 0; k < 10; k ++) {
			if (j + k >= m->nraw) {
				pplot[k] = NULL;
				continue;
			}
			for (i = 0; i < nummeas; i++)
				plot[k][i] = multimeas[i][j+k]/maxval[j+k];
			pplot[k] = plot[k];
		}
		for (i = 0; i < nummeas; i++)
			plot[10][i] = (double)i;
		plot_msg("Raw Bands %d - %d\n",j,j+9);
		do_plot10(plot[10], pplot[0], pplot[1], pplot[2], pplot[3], pplot[4], pplot[5], pplot[6], pplot[7], pplot[8], pplot[9], nummeas, 0);
	}
#endif	/* PATREC_DEBUG */

#ifdef NEVER
	/* Plot the shielded cell value */
	for (i = 0; i < nummeas; i++) { 
		plot[0][i] = multimeas[i][-1];
		plot[6][i] = (double)i;
	}
	plot_msg("Sheilded values\n");
	do_plot6(plot[6], plot[0], NULL, NULL, NULL, NULL, NULL, nummeas);
#endif

	/* Compute the range of each filter band */
	{
		double fbwidth;				/* Number of raw in each band */
		double st;

		fbwidth = (BH - BL)/(double)(NFB/2+1);

		for (st = BL, i = 0; i < NFB; i++) {
			fbands[i][0] = (int)(floor(st)); 
			fbands[i][1] = (int)(floor(st + fbwidth));
			st += 0.5 * fbwidth;
		}
//printf("fbwidth = %f\n",fbwidth);
//for (i = 0; i < NFB; i++) printf("~1 band %d is %d - %d\n",i,fbands[i][0],fbands[i][1]);
	}

	fraw = dmatrixz(0, nummeas-1, 0, NFB-1); 

	/* Weighted box average raw to create filtered bands */
	for (i = 0; i < nummeas; i++) {
		for (j = 0; j < NFB; j++) {
			fraw[i][j] = 0.0;
			for (k = fbands[j][0]; k < fbands[j][1]; k++) {
				fraw[i][j] += multimeas[i][k]/maxval[k];
			}
			fraw[i][j] /= (double)(fbands[j][1] - fbands[j][0]);
		}
	}

	slope = dvectorz(0, nummeas-1);  
	fslope = dvectorz(0, nummeas-1);  
	sizepop = ivectorz(0, nummeas-1);

	/* Compute slope result over readings and bands */
	/* Compute signed slope result over readings and bands */

#define FW 5		/* [3] Number of delta's to average */
	for (i = FW-1; i < (nummeas-FW); i++) {		/* Samples */
		double basl, bdev;		/* Best average slope, Best deviation */
		double sl[2 * FW -1];
		double asl[FW], dev[FW];		
		int slopen = 0;
		double slopeth = 0.0;
		int m, pp;

		for (pp = 0; pp < 2; pp++) { 			/* For each pass */

			for (j = 0; j < NFB; j++) {			/* For each band */

				/* Compute differences for the range of our windows */
				for (k = 0; k < (2 * FW -1); k++)
					sl[k] = fraw[i+k-FW+1][j] - fraw[i+k+-FW+2][j];

				/* For each window offset, compute average and deviation squared */
				bdev = 1e38;
				for (k = 0; k < FW; k++) { 

					/* Compute average of this window offset */
					asl[k] = 0.0;
					for (m = 0; m < FW; m++)	/* For slope in window */
						asl[k] += sl[k+m];
					asl[k] /= (double)FW;

					/* Compute deviation squared */
					dev[k] = 0.0;
					for (m = 0; m < FW; m++) {
						double tt;
						tt = sl[k+m] - asl[k];
						dev[k] += tt * tt;
					}
					if (dev[k] < bdev)
						bdev = dev[k];
				}

				/* Weight the deviations with a triangular weighting */
				/* to skew slightly towards the center */
				for (k = 0; k < FW; k++) { 
					double wt;
					wt = fabs(2.0 * k - (FW -1.0))/(FW-1.0);
					dev[k] += wt * bdev;
				}

				/* For each window offset, choose the one to use. */
				bdev = 1e38;
				basl = 0.0;
				for (k = 0; k < FW; k++) { 

					/* Choose window average with smallest deviation squared */
					if (dev[k] < bdev) {
						bdev = dev[k];
						basl = fabs(asl[k]);
					}
				}

				if (pp == 0) {		/* First pass, compute average slope over bands */
					slope[i] += basl;

				} else {			/* Second pass, average slopes of bands over threshold */
					if (basl > slopeth) {
						slope[i] += basl;
						slopen++;
					}
				}
			}	/* Next band */

			if (pp == 0) {
				slopeth = 1.0 * slope[i]/j;		/* Compute threshold */
				slope[i] = 0.0;
			} else {
				if (slopen > 0)
					slope[i] /= slopen;			/* Compute average of those over threshold */
			}
		}		/* Next pass */
	}
#undef FW

	/* Locate the minumum and maximum values */
	maxslope = 0.0;
	minslope = 1e38;
	for (i = 4; i < (nummeas-4); i++) {
		double avs;

		if (slope[i] > maxslope)
			maxslope = slope[i];

		/* Simple moving average for min comp. */
		avs = 0.0;
		for (j = -2; j <= 2; j++)
			avs += slope[i+j];
		avs /= 5.0;
		if (avs < minslope)
			minslope = avs;
	}

	/* Normalise the slope */
	maxslope *= 0.5;
	minslope *= 3.0;
	for (i = 0; i < nummeas; i++) {
		slope[i] = (slope[i] - minslope) / (maxslope - minslope);
		if (slope[i] < 0.0)
			slope[i] = 0.0;
		else if (slope[i] > 1.0)
			slope[i] = 1.0;
	}

	/* "Automatic Gain control" the raw slope information. */
#define LFW 40		/* [40] Half width of triangular filter */
	for (i = 0; i < nummeas; i++) {
		double sum, twt;
		
		sum = twt = 0.0;
		for (j = -LFW; j <= LFW; j++) {
			double wt;
			if ((i+j) < 0 || (i+j) >= nummeas)
				continue;

			wt = ((LFW-abs(j))/(double)LFW);
			
			sum += wt * slope[i+j];
			twt += wt;
		}
		fslope[i] = sum/twt;
		if (fslope[i] > fmaxslope)
			fmaxslope = fslope[i];
	}
#undef LFW

	/* Locate the minumum and maximum values */
	maxslope = 0.0;
	minslope = 1e38;
	for (i = 4; i < (nummeas-4); i++) {
		double avs;

		if (slope[i] > maxslope)
			maxslope = slope[i];

		/* Simple moving average for min comp. */
		avs = 0.0;
		for (j = -2; j <= 2; j++)
			avs += slope[i+j];
		avs /= 5.0;
		if (avs < minslope)
			minslope = avs;
	}

	/* Normalise the slope again */
	maxslope *= 0.3;
	minslope *= 3.0;
	for (i = 0; i < nummeas; i++) {
		slope[i] = (slope[i] - minslope) / (maxslope - minslope);
		if (slope[i] < 0.0)
			slope[i] = 0.0;
		else if (slope[i] > 1.0)
			slope[i] = 1.0;
	}


#ifdef PATREC_DEBUG
	plot_msg("Slope filter output + filtered raw:\n");
	for (j = 0; j < NFB; j++) {
		for (i = 0; i < nummeas; i++)
			plot[j][i] = fraw[i][j];
		pplot[j] = plot[j];
	}
	for (; j < 10; j++)
		pplot[j] = NULL;

	for (i = 0; i < nummeas; i++)
		plot[10][i] = (double)i;
	do_plot10(plot[10], slope, pplot[0], pplot[1], pplot[2], pplot[3], pplot[4], pplot[5], pplot[6], pplot[7], pplot[8], nummeas, 0);
#endif	/* PATREC_DEBUG */

	free_dvector(fslope, 0, nummeas-1);  

	/* Now threshold the measurements into possible patches */
	apat = 2 * nummeas;
	if ((pat = (munki_patch *)malloc(sizeof(munki_patch) * apat)) == NULL) {
		a1logd(p->log,1,"munki: malloc of patch structures failed!\n");
		free_dmatrix(fraw, 0, nummeas-1, 0, NFB-1);
		free_ivector(sizepop, 0, nummeas-1);
		free_dvector(slope, 0, nummeas-1);  
		free_dvector(maxval, -1, m->nraw-1);  
		return MUNKI_INT_MALLOC;
	}

	avglength = maxlength = 0.0;
	for (npat = i = 0; i < (nummeas-1); i++) {
		if (slope[i] > thresh)
			continue;

		/* Start of a new patch */
		if (npat >= apat) {
			apat *= 2;
			if ((pat = (munki_patch *)realloc(pat, sizeof(munki_patch) * apat)) == NULL) {
				free_dmatrix(fraw, 0, nummeas-1, 0, NFB-1);
				free_ivector(sizepop, 0, nummeas-1);
				free_dvector(slope, 0, nummeas-1);  
				free_dvector(maxval, -1, m->nraw-1);  
				a1logd(p->log,1,"munki: reallloc of patch structures failed!\n");
				return MUNKI_INT_MALLOC;
			}
		}
		pat[npat].ss = i;
		pat[npat].no = 2;
		pat[npat].use = 0;
		for (i++; i < (nummeas-1); i++) {
			if (slope[i] > thresh)
				break;
			pat[npat].no++;
		}
		avglength += (double) pat[npat].no;
		if (pat[npat].no > maxlength)
			maxlength = pat[npat].no;
		npat++;
	}
	a1logd(p->log,6,"Number of patches = %d\n",npat);

	/* We don't count the first and last patches, as we assume they are white leader. */
	/* (They are marked !use in list anyway) */
	if (npat < (tnpatch + 2)) {
		free_dmatrix(fraw, 0, nummeas-1, 0, NFB-1);
		free_ivector(sizepop, 0, nummeas-1);
		free_dvector(slope, 0, nummeas-1);  
		free_dvector(maxval, -1, m->nraw-1);  
		free(pat);
		a1logd(p->log,1,"Patch recog failed - unable to detect enough possible patches\n");
		return MUNKI_RD_NOTENOUGHPATCHES;
	} else if (npat >= (5 * tnpatch) + 2) {
		free_dmatrix(fraw, 0, nummeas-1, 0, NFB-1);
		free_ivector(sizepop, 0, nummeas-1);
		free_dvector(slope, 0, nummeas-1);  
		free_dvector(maxval, -1, m->nraw-1);  
		free(pat);
		a1logd(p->log,1,"Patch recog failed - detecting too many possible patches\n");
		return MUNKI_RD_TOOMANYPATCHES;
	}
	avglength /= (double)npat;

	if (p->log->debug >= 6) {
		for (i = 0; i < npat; i++) {
			a1logd(p->log,6,"Raw patch %d, start %d, length %d\n",i, pat[i].ss, pat[i].no);
		}
	}

	/* Check that the white leader and trailer agree with each other */
	for (k = 0; k < npat; k += npat-1) {
		double avg = 0.0;

		/* Average the samples that make up patch value */
		for (i = pat[k].ss; i < (pat[k].ss + pat[k].no); i++) {
			for (j = 1; j < m->nraw-1; j++)
				avg += multimeas[i][j];
		}
		avg /= (m->nraw-2.0);
		avg /= (double)pat[k].no; 

		if (k == 0)
			lead_avg = avg;
		else
			trail_avg = avg;
	}

	a1logd(p->log,2,"Leader avg = %f, trailer avg = %f\n",lead_avg,trail_avg);

	if (lead_avg/trail_avg < 0.7 || trail_avg/lead_avg < 0.7) {
		free_dmatrix(fraw, 0, nummeas-1, 0, NFB-1);
		free_ivector(sizepop, 0, nummeas-1);
		free_dvector(slope, 0, nummeas-1);  
		free_dvector(maxval, -1, m->nraw-1);  
		free(pat);
		a1logd(p->log,2,"Patch recog failed - swipe didn't start and end on the media\n");
		return MUNKI_RD_LEADTRAILINCONS;
	}

	/* Accumulate popularity ccount of possible patches */
	for (i = 1; i < (npat-1); i++)
		sizepop[pat[i].no]++;

	/* Locate the median potential patch width */
	msthr = npat - tnpatch;			/* Excess number of patches */
	if (msthr > 2)
		msthr = 2;
	msthr += tnpatch/2;				/* Expected median largest patch no. */	

	/* Search from largest to smallest for median */
	for (j = 0, i = maxlength; i > 0; i--) {
		j += sizepop[i];
		if (j >= msthr)
			break;
	}
	median = (double)i;

	a1logd(p->log,6,"Median patch width %f\n",median);

	/* Create a patch width fitting curve, to compensate for speed changes. */ 
	{
		co *points;
		datai glow;
		datai ghigh;
		int gres[1];

		points = (co *)malloc(sizeof(co) * (npat-2));
		pcurve = new_rspl(RSPL_NOFLAGS, 1, 1);
		
		if (points == NULL || pcurve == NULL) {
			free_dmatrix(fraw, 0, nummeas-1, 0, NFB-1);
			free_ivector(sizepop, 0, nummeas-1);
			free_dvector(slope, 0, nummeas-1);  
			free_dvector(maxval, -1, m->nraw-1);  
			free(pat);
			a1logd(p->log,2,"Patch recog failed - detecting too many possible patches (%d >= %d)\n",npat , 5 * tnpatch + 2);
			return MUNKI_INT_MALLOC;
		}

		for (k = i = 0; i < (npat-2); i++) {
			if (pat[i+1].no >= median/2
			 && pat[i+1].no <= 2 * median) {
				points[k].p[0] = pat[i+1].ss;
				points[k].v[0] = pat[i+1].no;
				k++;
			}
		}

		glow[0] = 0.0;
		ghigh[0] = nummeas-1.0;
		gres[0] = 256;

		if (k < 4) {
			pcurve->del(pcurve);
			pcurve = NULL;
			a1logd(p->log,6,"Not enough points to create pcurve (%d)\n",k);
		} else {
			pcurve->fit_rspl(pcurve, 0, points, k, 
			           glow, ghigh, gres, NULL, NULL,
			           5.0, NULL, NULL);

#ifdef PATREC_DEBUG
			plot_msg("Patch width fitting curve:\n");
			for (i = 0; i < nummeas; i++) {
				plot[0][i] = (double)i;
				plot[1][i] = pcurve->interp1(pcurve, plot[0][i]);
			}
			do_plot(plot[0], plot[1], NULL, NULL, nummeas);
#endif	/* PATREC_DEBUG */
		}
		free(points);
	}

	/* Compute speed normalized no */
	if (pcurve != NULL) {
		for (i = 1; i < (npat-1); i++)
			pat[i].nno = pat[i].no * median/pcurve->interp1(pcurve, (double)pat[i].ss);
		for (i = 1; i < (npat-1); i++)
			a1logd(p->log,6,"Patch %d, start %d, length %d, nno %.1f\n",i, pat[i].ss, pat[i].no, pat[i].nno);
		a1logd(p->log,6,"Normed median patch width %f\n",median);
	} else {
		for (i = 1; i < (npat-1); i++)
			pat[i].nno = pat[i].no;
	}

	/* Now decide which patches to use. */
	/* Try a widening window around the median. */
	max_window = 1.4;		/* Wider range if manual speed variance */
	for (window = 0.05, try = 0; window <= max_window; window *= 1.15, try++) {
		int bgcount = 0, bgstart = 0;
		int gcount, gstart;
		double wmin = median/(1.0 + window);
		double wmax = median * (1.0 + window);

		a1logd(p->log,6,"Window = %f - %f\n",wmin, wmax);
		/* Track which is the largest contiguous group that */
		/* is within our window */
		gcount = gstart = 0;
		for (i = 1; i < npat; i++) {
			double nno = pat[i].nno;

			if (i < (npat-1) && nno <= wmax) {		/* Small enough */
				if (nno >= wmin) {			/* And big enough */
					if (gcount == 0) {		/* Start of new group */
						gcount++;
						gstart = i;
						a1logd(p->log,6,"Start group at %d\n",gstart);
					} else {
						gcount++;			/* Continuing new group */
						a1logd(p->log,6,"Continue group at %d, count %d\n",gstart,gcount);
					}
				}
			} else {	/* Too big or end of patches, end this group */
				a1logd(p->log,6,"Terminating group group at %d, count %d\n",gstart,gcount);
				if (gcount > bgcount) {		/* New biggest group */
					bgcount = gcount;
					bgstart = gstart;
					a1logd(p->log,6,"New biggest\n");
				}
				gcount = gstart = 0;		/* End this group */
			}
		}
		a1logd(p->log,6,"Biggest group is at %d, count %d\n",bgstart,bgcount);

		if (bgcount == tnpatch) {			/* We're done */
			for (i = bgstart, fpat = j = 0; i < npat && j < tnpatch; i++) {
				double nno = pat[i].nno;

				if (nno <= wmax && nno >= wmin) {
					pat[i].use = 1;
					fpat++;
					j++;
					if (pat[i].no < MIN_SAMPLES) {
						a1logd(p->log,6,"Too few samples\n");
						free_dmatrix(fraw, 0, nummeas-1, 0, NFB-1);
						free_ivector(sizepop, 0, nummeas-1);
						free_dvector(slope, 0, nummeas-1);  
						free_dvector(maxval, -1, m->nraw-1);  
						free(pat);
						if (pcurve != NULL)
							pcurve->del(pcurve);
						a1logd(p->log,2,"Patch recog failed - patches sampled too sparsely\n");
						return MUNKI_RD_NOTENOUGHSAMPLES;
					}
				}
			}
			break;

		} else if (bgcount > tnpatch) {
			a1logd(p->log,6,"Too many patches\n");
			free_dmatrix(fraw, 0, nummeas-1, 0, NFB-1);
			free_ivector(sizepop, 0, nummeas-1);
			free_dvector(slope, 0, nummeas-1);  
			free_dvector(maxval, -1, m->nraw-1);  
			free(pat);
			if (pcurve != NULL)
				pcurve->del(pcurve);
			a1logd(p->log,2,"Patch recog failed - detected too many consistent patches\n");
			return MUNKI_RD_TOOMANYPATCHES;
		}
	}
	if (window > max_window) {
		a1logd(p->log,2,"Stopped window after %d tries at %f\n",try,window);
		a1logd(p->log,2,"Patch recog failed - unable to find enough consistent patches\n");
		free_dmatrix(fraw, 0, nummeas-1, 0, NFB-1);
		free_ivector(sizepop, 0, nummeas-1);
		free_dvector(slope, 0, nummeas-1);  
		free_dvector(maxval, -1, m->nraw-1);  
		free(pat);
		if (pcurve != NULL)
			pcurve->del(pcurve);
		a1logd(p->log,2,"Patch recog failed - unable to find enough consistent patches\n");
		return MUNKI_RD_NOTENOUGHPATCHES;
	}

	if (p->log->debug >= 6) {
		a1logd(p->log,2,"Stopped window after %d tries at %f\n",try,window);
		a1logd(p->log,6,"Got %d patches out of potential %d, want %d:\n",fpat, npat, tnpatch);
		a1logd(p->log,6,"Average patch length %f\n",avglength);
		for (j = 0, i = 1; i < (npat-1); i++) {
			if (pat[i].use == 0)
				continue;

			if (pcurve)
				a1logd(p->log,6,"Patch %d [ix %d], start %d, length %d (normlen %.1f)\n",j,i, pat[i].ss, pat[i].no, pat[i].nno);
			else
				a1logd(p->log,6,"Patch %d [ix %d], start %d, length %d\n",j,i, pat[i].ss, pat[i].no);
			j++;
		}
	}

	/* Now trim the patches simply by shrinking their windows */
	for (k = 1; k < (npat-1); k++) {
		int nno, trim;

		if (pat[k].use == 0)
			continue;

//		nno = (pat[k].no * 3 + 0)/4;		/* Trim to 75% & round down */
		nno = (pat[k].no * 2 + 0)/3;		/* Trim to 66% & round down [def] */
//		nno = (pat[k].no * 2 + 0)/4;		/* Trim to 50% & round down */
		trim = (pat[k].no - nno + 1)/2;

		pat[k].ss += trim;
		pat[k].no = nno;
	}

#ifdef PATREC_DEBUG
	a1logd(p->log,6,"After trimming got:\n");
	for (i = 1; i < (npat-1); i++) {
		if (pat[i].use == 0)
			continue;
		printf("Patch %d, start %d, length %d:\n",i, pat[i].ss, pat[i].no, pat[i].use);
	}

	/* Create fake "slope" value that marks patches */
	for (i = 0; i < nummeas; i++) 
		slope[i] = 1.0;
	for (k = 1; k < (npat-1); k++) {
		if (pat[k].use == 0)
			continue;
		for (i = pat[k].ss; i < (pat[k].ss + pat[k].no); i++)
			slope[i] = 0.0;
	}

	printf("Trimmed output:\n");
#ifdef PATREC_PLOT_ALLBANDS
	for (j = 0; j < (m->nraw-9); j += 9) 			/* Plot all the bands */
#else
	for (j = 24; j < (111-9); j += 30) 			/* Do some of the bands */
#endif
	{
		for (k = 0; k < 9; k ++) {
			if (j + k >= m->nraw) {
				pplot[k] = NULL;
				continue;
			}
			for (i = 0; i < nummeas; i++)
				plot[k][i] = multimeas[i][j+k]/maxval[j+k];
			pplot[k] = plot[k];
		}
		for (i = 0; i < nummeas; i++)
			plot[10][i] = (double)i;
		plot_msg("Raw Bands %d - %d\n",j,j+8);
		do_plot10(plot[10], slope, pplot[0], pplot[1], pplot[2], pplot[3], pplot[4], pplot[5], pplot[6], pplot[7], pplot[8], nummeas, 0);
	}

	free_dmatrix(plot, 0, 10, 0, nummeas-1);  
#endif	/* PATREC_DEBUG */

	/* Now compute averaged patch values */
	
	/* Compute average of (aproximate) white */
	white_avg = 0.0;
	for (j = 1; j < (m->nraw-1); j++) 
		white_avg += maxval[j];
	white_avg /= (m->nraw - 2.0);

	/* Now process the buffer values */
	for (i = 0; i < tnpatch; i++)
		for (j = 0; j < m->nraw; j++) 
			pavg[i][j] = 0.0;

	for (pix = 0, k = 1; k < (npat-1); k++) {
		double maxavg = -1e38;	/* Track min and max averages of readings for consistency */
		double minavg = 1e38;
		double cons;			/* Consistency */

		if (pat[k].use == 0)
			continue;

		if (pat[k].no <= MIN_SAMPLES) {
			a1logd(p->log,6,"Too few samples\n");
			free_dmatrix(fraw, 0, nummeas-1, 0, NFB-1);
			free_dvector(slope, 0, nummeas-1);  
			free_ivector(sizepop, 0, nummeas-1);
			free_dvector(maxval, -1, m->nraw-1);  
			free(pat);
			a1logd(p->log,1,"Patch recog failed - patches sampled too sparsely\n");
			return MUNKI_RD_NOTENOUGHSAMPLES;
		}

		/* Measure samples that make up patch value */
		for (i = pat[k].ss; i < (pat[k].ss + pat[k].no); i++) {
			double measavg = 0.0;

			for (j = 0; j < m->nraw; j++) {
				double val;

				val = multimeas[i][j];

				measavg += val;
				pavg[pix][j] += val;
			}
			measavg /= (m->nraw-2.0);
			if (measavg < minavg)
				minavg = measavg;
			if (measavg > maxavg)
				maxavg = measavg;
		}

		for (j = 0; j < m->nraw; j++) 
			pavg[pix][j] /= (double)pat[k].no;

		cons = (maxavg - minavg)/white_avg;
		a1logd(p->log,6,"Patch %d: consistency = %f%%, thresh = %f%%\n",pix,100.0 * cons, 100.0 * patch_cons_thr);
		if (cons > patch_cons_thr) {
			a1logd(p->log,1,"Patch recog failed - patch %d is inconsistent (%f%%)\n",pix, cons);
			rv |= 1;
		}
		pix++;
	}

	if (flags != NULL)
		*flags = rv;

	free_dmatrix(fraw, 0, nummeas-1, 0, NFB-1);
	free_dvector(slope, 0, nummeas-1);  
	free_ivector(sizepop, 0, nummeas-1);
	free_dvector(maxval, -1, m->nraw-1);  
	free(pat);		/* Otherwise caller will have to do it */

	a1logd(p->log,3,"munki_extract_patches_multimeas done, sat = %s, inconsist = %s\n",
	                  rv & 2 ? "true" : "false", rv & 1 ? "true" : "false");

	a1logd(p->log,2,"Patch recognition returning OK\n");

	return MUNKI_OK;
}

#undef BL
#undef BH
#undef BW

/* Recognise any flashes in the readings, and */
/* and average their values together as well as summing their duration. */
/* The readings are integrated, so the the units are cd/m^2 seconds. */
/* Return nz on an error */
/* (Doesn't extract [-1] shielded values, since they have already been used) */
munki_code munki_extract_patches_flash(
	munki *p,
	int *flags,				/* return flags */
	double *duration,		/* return duration */
	double *pavg,			/* return patch average [-1 nraw] */
	double **multimeas,		/* Array of [nummeas][-1 nraw] value to extract from */
	int nummeas,			/* number of readings made */
	double inttime			/* Integration time (used to compute duration) */
) {
	munkiimp *m = (munkiimp *)p->m;
	int i, j, k;
	double minval, maxval;		/* min and max input value at wavelength of maximum input */
	double mean;				/* Mean of the max wavelength band */
	int maxband;				/* Band of maximum value */
	double thresh;				/* Level threshold */
	int fsampl;					/* Index of the first sample over the threshold */
	int nsampl;					/* Number of samples over the threshold */
	double *aavg;				/* ambient average [-1 nraw] */
	double finttime;			/* Flash integration time */
	int rv = 0;
#ifdef PATREC_DEBUG
	double **plot;
#endif

	a1logd(p->log,3,"munki_extract_patches_flash: %d measurements\n",nummeas);

	/* Discover the maximum input value for flash dection */
	maxval = -1e6;
	maxband = 0;
	for (j = 0; j < m->nraw; j ++) {
		for (i = 0; i < nummeas; i++) {
			if (multimeas[i][j] > maxval) {
				maxval = multimeas[i][j];
				maxband = j;
			}
		}
	}

	if (maxval <= 0.0) {
		a1logd(p->log,1,"No flashes found in measurement\n");
		return MUNKI_RD_NOFLASHES;
	}

	minval = 1e6;
	mean = 0.0;
	for (i = 0; i < nummeas; i++) {
		mean += multimeas[i][maxband];
		if (multimeas[i][maxband] < minval)
			minval = multimeas[i][maxband];
	}
	mean /= (double)nummeas;

	/* Set the threshold at 5% from mean towards max */
	thresh = (3.0 * mean + maxval)/4.0;
	a1logd(p->log,6,"munki_extract_patches_flash band %d minval %f maxval %f, mean = %f, thresh = %f\n",maxband,minval,maxval,mean, thresh);

#ifdef PATREC_DEBUG
	/* Plot out 6 lots of 6 values each */ 
	plot = dmatrixz(0, 6, 0, nummeas-1);  
	for (j = maxband -3; j>= 0 && j < (m->nraw-6); j += 100)		/* Do one set around max */
	{
		for (k = 0; k < 6; k ++) {
			for (i = 0; i < nummeas; i++) { 
				plot[k][i] = multimeas[i][j+k]/maxval;
			}
		}
		for (i = 0; i < nummeas; i++)
			plot[6][i] = (double)i;
		plot_msg("Bands %d - %d\n",j,j+5);
		do_plot6(plot[6], plot[0], plot[1], plot[2], plot[3], plot[4], plot[5], nummeas);
	}
	free_dmatrix(plot,0,6,0,nummeas-1);
#endif

#ifdef PATREC_DEBUG
	/* Plot just the pulses */
	{
		int start, end;

		plot = dmatrixz(0, 6, 0, nummeas-1);  

		for(j = 0, start = -1, end = 0;;) {

			for (start = -1, i = end; i < nummeas; i++) {
				if (multimeas[i][maxband] >= thresh) {
					if (start < 0)
						start = i;
				} else if (start >= 0) {
					end = i;
					break;
				}
			}
			if (start < 0)
				break;
			start -= 3;
			if (start < 0)
				start = 0;
			end += 4;
			if (end > nummeas)
				end = nummeas;
	
			for (i = start; i < end; i++, j++) { 
				int q;

				plot[6][j] = (double)j;
#ifdef NEVER	/* Plot +/-3 around maxband */
				for (q = 0, k = maxband -3; k < (maxband+3) && k >= 0 && k < m->nraw; k++, q++) {
					plot[q][j] = multimeas[i][k]/maxval;
				}
#else
				/* plot max of bands in 6 segments */
				for (q = 0; q < 6; q++) {
					int ss, ee;

					plot[q][j] = -1e60;
					ss = q * (m->nraw/6);
					ee = (q+1) * (m->nraw/6);
					for (k = ss; k < ee; k++) {
						if (multimeas[i][k]/maxval > plot[q][j])
							plot[q][j] = multimeas[i][k]/maxval;
					} 
				}
#endif
			}
		}
		do_plot6(plot[6], plot[0], plot[1], plot[2], plot[3], plot[4], plot[5], j);
		free_dmatrix(plot,0,6,0,nummeas-1);
	}
#endif

	/* Locate the first sample over the threshold, and the */
	/* total number of samples in the pulses. */
	fsampl = -1;
	for (nsampl = i = 0; i < nummeas; i++) {
		for (j = 0; j < m->nraw; j++) {
			if (multimeas[i][j] >= thresh)
				break;
		}
		if (j < m->nraw) {
			if (fsampl < 0)
				fsampl = i;
			nsampl++;
		}
	}
	a1logd(p->log,6,"Number of flash patches = %d\n",nsampl);
	if (nsampl == 0)
		return MUNKI_RD_NOFLASHES;

	/* See if there are as many samples before the first flash */
	if (nsampl < 6)
		nsampl = 6;

	/* Average nsample samples of ambient */
	i = (fsampl-3-nsampl);
	if (i < 0)
		return MUNKI_RD_NOAMBB4FLASHES;
	a1logd(p->log,6,"Ambient samples %d to %d \n",i,fsampl-3);
	aavg = dvectorz(-1, m->nraw-1);  
	for (nsampl = 0; i < (fsampl-3); i++) {
		for (j = 0; j < m->nraw; j++)
			aavg[j] += multimeas[i][j];
		nsampl++;
	}

	/* Integrate all the values over the threshold, */
	/* and also one either side of flash */
	for (j = 0; j < m->nraw; j++)
		pavg[j] = 0.0;

	for (k = 0, i = 1; i < (nummeas-1); i++) {
		int sample = 0;
		for (j = 0; j < m->nraw; j++) {
			if (multimeas[i-1][j] >= thresh) {
				sample = 1;
				break;
			}
			if (multimeas[i][j] >= thresh) {
				sample = 1;
				break;
			}
			if (multimeas[i+1][j] >= thresh) {
				sample = 1;
				break;
			}
		}
		if (j < m->nraw) {
			a1logd(p->log,6,"Integrating flash sample no %d \n",i);
			for (j = 0; j < m->nraw; j++)
				pavg[j] += multimeas[i][j];
			k++;
		}
	}
	for (j = 0; j < m->nraw; j++)
		pavg[j] = pavg[j]/(double)k - aavg[j]/(double)nsampl;

	a1logd(p->log,6,"Number of flash patches integrated = %d\n",k);

	finttime = inttime * (double)k;
	if (duration != NULL)
		*duration = finttime;

	/* Convert to cd/m^2 seconds */
	for (j = 0; j < m->nraw; j++)
		pavg[j] *= finttime;

	if (flags != NULL)
		*flags = rv;

	free_dvector(aavg, -1, m->nraw-1);  

	return MUNKI_OK;
}

/* Convert an absraw array from raw wavelengths to output wavelenths */
/* for the current resolution. Apply stray light compensation too. */
void munki_absraw_to_abswav(
	munki *p,
	int nummeas,			/* Return number of readings measured */
	double **abswav,		/* Desination array [nwav] */
	double **absraw			/* Source array [-1 nraw] */
) {
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	double *tm;			/* Temporary array */
	int i, j, k, cx, sx;
	
	tm = dvector(0, m->nwav-1);

	/* For each measurement */
	for (i = 0; i < nummeas; i++) {

		/* For each output wavelength */
		for (cx = j = 0; j < m->nwav; j++) {
			double oval = 0.0;
	
			/* For each matrix value */
			if (s->reflective) {
				sx = m->rmtx_index[j];		/* Starting index */
				for (k = 0; k < m->rmtx_nocoef[j]; k++, cx++, sx++)
					oval += m->rmtx_coef[cx] * absraw[i][sx];
			} else {
				sx = m->emtx_index[j];		/* Starting index */
				for (k = 0; k < m->emtx_nocoef[j]; k++, cx++, sx++)
					oval += m->emtx_coef[cx] * absraw[i][sx];
			}
			tm[j] = oval;
		}

		/* Now apply stray light compensation */ 
		/* For each output wavelength */
		for (j = 0; j < m->nwav; j++) {
			double oval = 0.0;
	
			/* For each matrix value */
			for (k = 0; k < m->nwav; k++)
				oval += m->straylight[j][k] * tm[k];
			abswav[i][j] = oval;
		}
	}
	free_dvector(tm, 0, m->nwav-1);
}

/* Convert an absraw array from raw wavelengths to output wavelenths */
/* for the standard resolution. Apply stray light compensation too. */
void munki_absraw_to_abswav1(
	munki *p,
	int nummeas,			/* Return number of readings measured */
	double **abswav,		/* Desination array [nwav1] */
	double **absraw		/* Source array [-1 nraw] */
) {
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	double *tm;			/* Temporary array */
	int i, j, k, cx, sx;
	
	tm = dvector(0, m->nwav1-1);

	/* For each measurement */
	for (i = 0; i < nummeas; i++) {

		/* For each output wavelength */
		for (cx = j = 0; j < m->nwav1; j++) {
			double oval = 0.0;
	
			/* For each matrix value */
			if (s->reflective) {
				sx = m->rmtx_index1[j];		/* Starting index */
				for (k = 0; k < m->rmtx_nocoef1[j]; k++, cx++, sx++)
					oval += m->rmtx_coef1[cx] * absraw[i][sx];
			} else {
				sx = m->emtx_index1[j];		/* Starting index */
				for (k = 0; k < m->emtx_nocoef1[j]; k++, cx++, sx++)
					oval += m->emtx_coef1[cx] * absraw[i][sx];
			}
			tm[j] = oval;
		}

		/* Now apply stray light compensation */ 
		/* For each output wavelength */
		for (j = 0; j < m->nwav1; j++) {
			double oval = 0.0;
	
			/* For each matrix value */
			for (k = 0; k < m->nwav1; k++)
				oval += m->straylight1[j][k] * tm[k];
			abswav[i][j] = oval;
		}
	}
	free_dvector(tm, 0, m->nwav1-1);
}

/* Convert an absraw array from raw wavelengths to output wavelenths */
/* for the high resolution. Apply light compensation too. */
void munki_absraw_to_abswav2(
	munki *p,
	int nummeas,			/* Return number of readings measured */
	double **abswav,		/* Desination array [nwav2] */
	double **absraw			/* Source array [-1 nraw] */
) {
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	double *tm;			/* Temporary array */
	int i, j, k, cx, sx;
	
	tm = dvector(0, m->nwav2-1);

	/* For each measurement */
	for (i = 0; i < nummeas; i++) {

		/* For each output wavelength */
		for (cx = j = 0; j < m->nwav2; j++) {
			double oval = 0.0;
	
			/* For each matrix value */
			if (s->reflective) {
				sx = m->rmtx_index2[j];		/* Starting index */
				for (k = 0; k < m->rmtx_nocoef2[j]; k++, cx++, sx++)
					oval += m->rmtx_coef2[cx] * absraw[i][sx];
			} else {
				sx = m->emtx_index2[j];		/* Starting index */
				for (k = 0; k < m->emtx_nocoef2[j]; k++, cx++, sx++)
					oval += m->emtx_coef2[cx] * absraw[i][sx];
			}
			tm[j] = oval;
		}

		/* Now apply stray light compensation */ 
		/* For each output wavelength */
		for (j = 0; j < m->nwav2; j++) {
			double oval = 0.0;
	
			/* For each matrix value */
			for (k = 0; k < m->nwav2; k++)
				oval += m->straylight2[j][k] * tm[k];
			abswav[i][j] = oval;
		}
	}
	free_dvector(tm, 0, m->nwav2-1);
}

/* Convert an abswav array of output wavelengths to scaled output readings. */
void munki_scale_specrd(
	munki *p,
	double **outspecrd,		/* Destination */
	int numpatches,			/* Number of readings/patches */
	double **inspecrd		/* Source */
) {
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	int i, j;
	
	/* For each measurement */
	for (i = 0; i < numpatches; i++) {

		/* For each output wavelength */
		for (j = 0; j < m->nwav; j++) {
			outspecrd[i][j] = inspecrd[i][j] * s->cal_factor[j];
		}
	}
}


/* =============================================== */
#ifdef HIGH_RES

/* High res congiguration */
#undef EXISTING_SHAPE		/* [und] Else generate filter shape */
#define USE_GAUSSIAN		/* [def] Use gaussian filter shape, else lanczos2 */

#define DO_CCDNORM			/* [def] Normalise CCD values to original */
#undef DO_CCDNORMAVG		/* [und] Normalise averages rather than per CCD bin */
#define BOX_INTEGRATE    	/* [und] Integrate raw samples as if they were +/-0.5 boxes */
							/*       (This improves coeficient consistency a bit ?) */

#ifdef NEVER
/* Plot the matrix coefficients */
void munki_debug_plot_mtx_coef(munki *p, int ref) {
	munkiimp *m = (munkiimp *)p->m;
	int i, j, k, cx, sx;
	double *xx, *ss;
	double **yy;

	xx = dvectorz(-1, m->nraw-1);		/* X index */
	yy = dmatrixz(0, 5, -1, m->nraw-1);	/* Curves distributed amongst 5 graphs */
	
	for (i = 0; i < m->nraw; i++)
		xx[i] = i;

	/* For each output wavelength */
	for (cx = j = 0; j < m->nwav; j++) {
		i = j % 5;

//		printf("Out wave = %d\n",j);
		/* For each matrix value */
		if (ref) {
			sx = m->rmtx_index[j];		/* Starting index */
//			printf("start index = %d, nocoef %d\n",sx,m->rmtx_nocoef[j]);
			for (k = 0; k < m->rmtx_nocoef[j]; k++, cx++, sx++) {
//				printf("offset %d, coef ix %d val %f from ccd %d\n",k, cx, m->rmtx_coef[cx], sx);
				yy[5][sx] += 0.5 * m->rmtx_coef[cx];
				yy[i][sx] = m->rmtx_coef[cx];
			}
		} else {
			sx = m->emtx_index[j];		/* Starting index */
//			printf("start index = %d, nocoef %d\n",sx,m->emtx_nocoef[j]);
			for (k = 0; k < m->emtx_nocoef[j]; k++, cx++, sx++) {
//				printf("offset %d, coef ix %d val %f from ccd %d\n",k, cx, m->emtx_coef[cx], sx);
				yy[5][sx] += 0.5 * m->emtx_coef[cx];
				yy[i][sx] = m->emtx_coef[cx];
			}
		}
	}

	if (ref)
		plot_msg("Reflective cooeficients\n");
	else
		plot_msg("Emissive cooeficients\n");
	do_plot6(xx, yy[0], yy[1], yy[2], yy[3], yy[4], yy[5], m->nraw);
	free_dvector(xx, -1, m->nraw-1);
	free_dmatrix(yy, 0, 2, -1, m->nraw-1);
}
#endif

/* Filter shape point */
typedef	struct {
	double wl, we;
} munki_fs;

/* Filter cooeficient values */
typedef struct {
	int ix;				/* Raw index */
	double we;			/* Weighting */
} munki_fc;

/* Wavelenth calibration crossover point information */
typedef struct {
	double wav;				/* Wavelength of point */
	double raw;				/* Raw index of point */		
	double wei;				/* Weigting of the point */
} munki_xp;

/* Linearly interpolate the filter shape */
static double lin_fshape(munki_fs *fsh, int n, double x) {
	int i;
	double y;

	if (x <= fsh[0].wl)
		return fsh[0].we;
	else if (x >= fsh[n-1].wl)
		return fsh[n-1].we;

	for (i = 0; i < (n-1); i++)
		if (x >= fsh[i].wl && x <= fsh[i+1].wl)
			break;

	x = (x - fsh[i].wl)/(fsh[i+1].wl - fsh[i].wl);
	y = fsh[i].we + (fsh[i+1].we - fsh[i].we) * x;
	
	return y;
}

/* Generate a sample from a lanczos2 filter shape */
/* wi is the width of the filter */
static double lanczos2(double wi, double x) {
	double y;

#ifdef USE_GAUSSIAN
	/* gausian */
	wi = wi/(sqrt(2.0 * log(2.0)));	/* Convert width at half max to std. dev. */
    x = x/wi;
//	y = 1.0/(wi * sqrt(2.0 * DBL_PI)) * exp(-(x * x));		/* Unity area */
	y = exp(-(x * x));										/* Center at 1.0 */
#else


	/* lanczos2 */
	wi *= 1.05;			// Improves smoothness. Why ?
	x = fabs(1.0 * x/wi);
	if (x >= 2.0)
		return 0.0; 
	if (x < 1e-5)
		return 1.0;
	y = sin(DBL_PI * x)/(DBL_PI * x) * sin(DBL_PI * x/2.0)/(DBL_PI * x/2.0);
#endif
	return y;
}

#if defined(__APPLE__) && defined(__POWERPC__)

/* Workaround for a ppc gcc 3.3 optimiser bug... */
static int gcc_bug_fix(int i) {
	static int nn;
	nn += i;
	return nn;
}
#endif	/* APPLE */

#ifdef SALONEINSTLIB
# define ONEDSTRAYLIGHTUS
#endif

/* Create high resolution mode references, */
/* Create Reflective if ref nz, else create Emissive */
/* We expect this to be called twice, once for each. */
munki_code munki_create_hr(munki *p, int ref) {
	munkiimp *m = (munkiimp *)p->m;
	int i, j, jj, k, cx, sx;
	munki_fc coeff[40][16];	/* Existing filter cooefficients */
	int nwav1;					/* Number of filters */
	double wl_short1, wl_long1;	/* Output wavelength of first and last filters */
	double wl_step1;
	munki_xp xp[41];			/* Crossover points each side of filter */
	munki_code ev = MUNKI_OK;
	rspl *raw2wav;				/* Lookup from CCD index to wavelength */
	munki_fs fshape[40 * 16];  /* Existing filter shape */
	int ncp = 0;				/* Number of shape points */
	int *mtx_index1, **pmtx_index2, *mtx_index2;
	int *mtx_nocoef1, **pmtx_nocoef2, *mtx_nocoef2;
	double *mtx_coef1, **pmtx_coef2, *mtx_coef2;

	double min_wl = ref ? WL_REF_MIN : WL_EMIS_MIN;
	
	/* Start with nominal values. May alter these if filters are not unique */
	nwav1 = m->nwav1;
	wl_short1 = m->wl_short1;
	wl_long1 = m->wl_long1;
	wl_step1 = (wl_long1 - m->wl_short1)/(m->nwav1-1.0);

	if (ref) {
		mtx_index1 = m->rmtx_index1;
		mtx_nocoef1 = m->rmtx_nocoef1;
		mtx_coef1 = m->rmtx_coef1;
		mtx_index2 = NULL;
		mtx_nocoef2 = NULL;
		mtx_coef2 = NULL;
		pmtx_index2 = &m->rmtx_index2;
		pmtx_nocoef2 = &m->rmtx_nocoef2;
		pmtx_coef2 = &m->rmtx_coef2;
	} else {
		mtx_index1 = m->emtx_index1;
		mtx_nocoef1 = m->emtx_nocoef1;
		mtx_coef1 = m->emtx_coef1;
		mtx_index2 = NULL;
		mtx_nocoef2 = NULL;
		mtx_coef2 = NULL;
		pmtx_index2 = &m->emtx_index2;
		pmtx_nocoef2 = &m->emtx_nocoef2;
		pmtx_coef2 = &m->emtx_coef2;
	}
	
	/* Convert the native filter cooeficient representation to */
	/* a 2D array we can randomly index. Skip any duplicated */
	/* filter cooeficients. */
	for (cx = j = jj = 0; j < m->nwav1; j++) { /* For each output wavelength */
		if (j >= 40) {	/* Assert */
			a1logw(p->log,"munki: number of output wavelenths is > 40\n");
			return MUNKI_INT_ASSERT;
		}

		/* For each matrix value */
		sx = mtx_index1[j];		/* Starting index */

		if (j < (m->nwav1-2)		/* Skip duplicates + last */
		  && sx == mtx_index1[j+1]
		  && mtx_nocoef1[j] == mtx_nocoef1[j+1]) {
//printf("~1 skipping %d\n",j);

			wl_short1 += wl_step1;
			nwav1--;
			cx += mtx_nocoef1[j];
			continue;
		}
		for (k = 0; k < mtx_nocoef1[j]; k++, cx++, sx++) {
			if (k >= 16) {	/* Assert */
				a1logw(p->log,"munki: number of filter coeefs is > 16\n");
				return MUNKI_INT_ASSERT;
			}

			coeff[jj][k].ix = sx;
			coeff[jj][k].we = mtx_coef1[cx];
		}
		jj++;
	}

#ifdef HIGH_RES_PLOT
	/* Plot original re-sampling curves */
	{
		double *xx, *ss;
		double **yy;

		xx = dvectorz(-1, m->nraw-1);		/* X index */
		yy = dmatrixz(0, 5, -1, m->nraw-1);	/* Curves distributed amongst 5 graphs */
		
		for (i = 0; i < m->nraw; i++)
			xx[i] = i;

		/* For each output wavelength */
		for (j = 0; j < nwav1; j++) {
			i = j % 5;

			/* For each matrix value */
			for (k = 0; k < mtx_nocoef1[j]; k++) {
				yy[5][coeff[j][k].ix] += 0.5 * coeff[j][k].we;
				yy[i][coeff[j][k].ix] = coeff[j][k].we;
			}
		}

		if (ref)
			plot_msg("Original reflection wavelength sampling curves:\n");
		else
			plot_msg("Original emission wavelength sampling curves:\n");
		do_plot6(xx, yy[0], yy[1], yy[2], yy[3], yy[4], yy[5], m->nraw);
		free_dvector(xx, -1, m->nraw-1);
		free_dmatrix(yy, 0, 2, -1, m->nraw-1);
	}
#endif /* HIGH_RES_PLOT */

//	a1logd(p->log,3,"computing crossover points\n");
	/* Compute the crossover points between each filter */
	for (i = 0; i < (nwav1-1); i++) {
		double den, y1, y2, y3, y4, yn, xn;	/* Location of intersection */
		double eps = 1e-6;			/* Numerical tollerance */
		double besty = -1e6;

		/* between filter i and i+1, we want to find the two */
		/* raw indexes where the weighting values cross over */
		/* Do a brute force search to avoid making assumptions */
		/* about the raw order. */
		for (j = 0; j < (mtx_nocoef1[i]-1); j++) {
			for (k = 0; k < (mtx_nocoef1[i+1]-1); k++) {
				if (coeff[i][j].ix == coeff[i+1][k].ix
				 && coeff[i][j+1].ix == coeff[i+1][k+1].ix) {

//					a1logd(p->log,3,"got it at %d, %d: %d = %d, %d = %d\n",j,k, coeff[i][j].ix, coeff[i+1][k].ix, coeff[i][j+1].ix, coeff[i+1][k+1].ix);

					/* Compute the intersection of the two line segments */
					y1 = coeff[i][j].we;
					y2 = coeff[i][j+1].we;
					y3 = coeff[i+1][k].we;
					y4 = coeff[i+1][k+1].we;
//					a1logd(p->log,3,"y1 %f, y2 %f, y3 %f, y4 %f\n",y1, y2, y3, y4);
					den = -y4 + y3 + y2 - y1;
					if (fabs(den) < eps)
						continue; 
					yn = (y2 * y3 - y1 * y4)/den;
					xn = (y3 - y1)/den;
					if (xn < -eps || xn > (1.0 + eps))
						continue;
//					a1logd(p->log,3,"den = %f, yn = %f, xn = %f\n",den,yn,xn);
					if (yn > besty) {
						xp[i+1].wav = XSPECT_WL(wl_short1, wl_long1, nwav1, i + 0.5);
						xp[i+1].raw = (1.0 - xn) * coeff[i][j].ix + xn * coeff[i][j+1].ix;
						xp[i+1].wei = yn;
						besty = yn;
//					a1logd(p->log,3,"Intersection %d: wav %f, raw %f, wei %f\n",i+1,xp[i+1].wav,xp[i+1].raw,xp[i+1].wei);
//					a1logd(p->log,3,"Found new best y %f\n",yn);
					}
//				a1logd(p->log,3,"\n");
				}
			}
		}
		if (besty < 0.0) {	/* Assert */
			a1logw(p->log,"munki: failed to locate crossover between resampling filters\n");
			return MUNKI_INT_ASSERT;
		}
//		a1logd(p->log,3,"\n");
	}

	/* Add the two points for the end filters */
	{
		double x5, x6, y5, y6;				/* Points on intesecting line */
		double den, y1, y2, y3, y4, yn, xn;	/* Location of intersection */

		x5 = xp[1].raw;
		y5 = xp[1].wei;
		x6 = xp[2].raw;
		y6 = xp[2].wei;

		/* Search for possible intersection point with first curve */
		/* Create equation for line from next two intersection points */
		for (j = 0; j < (mtx_nocoef1[0]-1); j++) {
			/* Extrapolate line to this segment */
			y3 = y5 + (coeff[0][j].ix - x5)/(x6 - x5) * (y6 - y5);
			y4 = y5 + (coeff[0][j+1].ix - x5)/(x6 - x5) * (y6 - y5);
			/* This segment of curve */
			y1 = coeff[0][j].we;
			y2 = coeff[0][j+1].we;
			if ( ((  y1 >= y3 && y2 <= y4)		/* Segments overlap */
			   || (  y1 <= y3 && y2 >= y4))
			  && ((    coeff[0][j].ix < x5 && coeff[0][j].ix < x6
			        && coeff[0][j+1].ix < x5 && coeff[0][j+1].ix < x6)
			   || (    coeff[0][j+1].ix > x5 && coeff[0][j+1].ix > x6
				    && coeff[0][j].ix > x5 && coeff[0][j].ix > x6))) {
				break;
			}
		}
		if (j >= mtx_nocoef1[0]) {	/* Assert */
			a1logw(p->log,"munki: failed to find end crossover\n");
			return MUNKI_INT_ASSERT;
		}
		den = -y4 + y3 + y2 - y1;
		yn = (y2 * y3 - y1 * y4)/den;
		xn = (y3 - y1)/den;
//		a1logd(p->log,3,"den = %f, yn = %f, xn = %f\n",den,yn,xn);
		xp[0].wav = XSPECT_WL(wl_short1, wl_long1, nwav1, -0.5);
		xp[0].raw = (1.0 - xn) * coeff[0][j].ix + xn * coeff[0][j+1].ix;
		xp[0].wei = yn;
//		a1logd(p->log,3,"End 0 intersection %d: wav %f, raw %f, wei %f\n",0,xp[0].wav,xp[0].raw,xp[0].wei);
//		a1logd(p->log,3,"\n");

		x5 = xp[nwav1-2].raw;
		y5 = xp[nwav1-2].wei;
		x6 = xp[nwav1-1].raw;
		y6 = xp[nwav1-1].wei;

//		a1logd(p->log,3,"x5 %f, y5 %f, x6 %f, y6 %f\n",x5,y5,x6,y6);
		/* Search for possible intersection point with first curve */
		/* Create equation for line from next two intersection points */
		for (j = 0; j < (mtx_nocoef1[0]-1); j++) {
			/* Extrapolate line to this segment */
			y3 = y5 + (coeff[nwav1-1][j].ix - x5)/(x6 - x5) * (y6 - y5);
			y4 = y5 + (coeff[nwav1-1][j+1].ix - x5)/(x6 - x5) * (y6 - y5);
			/* This segment of curve */
			y1 = coeff[nwav1-1][j].we;
			y2 = coeff[nwav1-1][j+1].we;
			if ( ((  y1 >= y3 && y2 <= y4)		/* Segments overlap */
			   || (  y1 <= y3 && y2 >= y4))
			  && ((    coeff[nwav1-1][j].ix < x5 && coeff[nwav1-1][j].ix < x6
			        && coeff[nwav1-1][j+1].ix < x5 && coeff[nwav1-1][j+1].ix < x6)
			   || (    coeff[nwav1-1][j+1].ix > x5 && coeff[nwav1-1][j+1].ix > x6
				    && coeff[nwav1-1][j].ix > x5 && coeff[nwav1-1][j].ix > x6))) {
				break;
			}
		}
		if (j >= mtx_nocoef1[nwav1-1]) {	/* Assert */
			a1logw(p->log, "munki: failed to find end crossover\n");
			return MUNKI_INT_ASSERT;
		}
		den = -y4 + y3 + y2 - y1;
		yn = (y2 * y3 - y1 * y4)/den;
		xn = (y3 - y1)/den;
//		a1logd(p->log,3,"den = %f, yn = %f, xn = %f\n",den,yn,xn);
		xp[nwav1].wav = XSPECT_WL(wl_short1, wl_long1, nwav1, nwav1-0.5);
		xp[nwav1].raw = (1.0 - xn) * coeff[nwav1-1][j].ix + xn * coeff[nwav1-1][j+1].ix;
		xp[nwav1].wei = yn;
//		a1logd(p->log,3,"End 36 intersection %d: wav %f, raw %f, wei %f\n",nwav1+1,xp[nwav1].wav,xp[nwav1].raw,xp[nwav1].wei);
//		a1logd(p->log,3,"\n");
	}

#ifdef HIGH_RES_PLOT
	/* Plot original re-sampling curves + crossing points */
	{
		double *xx, *ss;
		double **yy;
		double *xc, *yc;

		xx = dvectorz(-1, m->nraw-1);		/* X index */
		yy = dmatrixz(0, 5, -1, m->nraw-1);	/* Curves distributed amongst 5 graphs */
		xc = dvectorz(0, nwav1);		/* Crossover X */
		yc = dvectorz(0, nwav1);		/* Crossover Y */
		
		for (i = 0; i < m->nraw; i++)
			xx[i] = i;

		/* For each output wavelength */
		for (j = 0; j < nwav1; j++) {
			i = j % 5;

			/* For each matrix value */
			for (k = 0; k < mtx_nocoef1[j]; k++) {
				yy[5][coeff[j][k].ix] += 0.5 * coeff[j][k].we;
				yy[i][coeff[j][k].ix] = coeff[j][k].we;
			}
		}

		/* Crosses at intersection points */
		for (i = 0; i <= nwav1; i++) {
			xc[i] = xp[i].raw;
			yc[i] = xp[i].wei;
		}

		if (ref)
			plot_msg("Original reflection sampling curves + crossover points\n");
		else
			plot_msg("Original emsission sampling curves + crossover points\n");
		do_plot6p(xx, yy[0], yy[1], yy[2], yy[3], yy[4], yy[5], m->nraw, xc, yc, nwav1+1);
		free_dvector(xx, -1, m->nraw-1);
		free_dmatrix(yy, 0, 2, -1, m->nraw-1);
		free_dvector(xc, 0, nwav1);
		free_dvector(yc, 0, nwav1);
	}
#endif /* HIGH_RES_PLOT */

#ifdef HIGH_RES_DEBUG
	/* Check to see if the area of each filter curve is the same */
	/* (yep, width times 2 * xover height is close to 1.0, and the */
	/*  sum of the weightings is exactly 1.0) */
	for (i = 0; i < nwav1; i++) {
		double area1, area2;
		area1 = fabs(xp[i].raw - xp[i+1].raw) * (xp[i].wei + xp[i+1].wei);

		area2 = 0.0;
		for (j = 0; j < (mtx_nocoef1[i]); j++)
		    area2 += coeff[i][j].we;

		printf("Area of curve %d = %f, %f\n",i,area1, area2);
	}
#endif /* HIGH_RES_DEBUG */

	/* From our crossover data, create a rspl that maps raw CCD index */
	/* value into wavelength. */
	{
		co sd[101];				/* Scattered data points */
		datai glow, ghigh;
		datao vlow, vhigh;
		int gres[1];
		double avgdev[1];

		if ((raw2wav = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) {
			a1logd(p->log,3,"munki: creating rspl for high res conversion failed\n");
			return MUNKI_INT_NEW_RSPL_FAILED;
		}

		vlow[0] = 1e6;
		vhigh[0] = -1e6;

		for (i = 0; i < (nwav1+1); i++) {
			sd[i].p[0] = xp[i].raw;
			sd[i].v[0] = xp[i].wav;

			if (sd[i].v[0] < vlow[0])
				vlow[0] = sd[i].v[0];
			if (sd[i].v[0] > vhigh[0])
				vhigh[0] = sd[i].v[0];
		}
		glow[0] = 0.0;
		ghigh[0] = (double)(m->nraw-1);
		gres[0] = m->nraw;
		avgdev[0] = 0.0;
		
		raw2wav->fit_rspl(raw2wav, 0, sd, nwav1+1, glow, ghigh, gres, vlow, vhigh, 1.0, avgdev, NULL);
	}

#ifdef EXISTING_SHAPE
	/* Convert each weighting curves values into normalized values and */
	/* accumulate into a single curve. */
	/* This probably isn't quite correct - we really need to remove */
	/* the effects of the convolution with the CCD cell widths. */
	/* perhaps it's closer to a lanczos2 if this were done ? */
	{
		for (i = 0; i < nwav1; i++) {
			double cwl;		/* center wavelength */
			double weight = 0.0;

			for (j = 0; j < (mtx_nocoef1[i]); j++) {
				double w1, w2, cellw;
				co pp;

				/* Translate CCD cell boundaries index to wavelength */
				pp.p[0] = (double)coeff[i][j].ix - 0.5; 
				raw2wav->interp(raw2wav, &pp);
				w1 = pp.v[0];

				pp.p[0] = (double)coeff[i][j].ix + 0.5; 
				raw2wav->interp(raw2wav, &pp);
				w2 = pp.v[0];

				cellw = fabs(w2 - w1);

				cwl = XSPECT_WL(wl_short1, wl_long1, nwav1, i);

				/* Translate CCD index to wavelength */
				pp.p[0] = (double)coeff[i][j].ix; 
				raw2wav->interp(raw2wav, &pp);
				fshape[ncp].wl = pp.v[0] - cwl;
				fshape[ncp].we = coeff[i][j].we / (0.09 * cellw);
				ncp++;
			}
		}

		/* Now sort by wavelength */
#define HEAP_COMPARE(A,B) (A.wl < B.wl)
		HEAPSORT(munki_fs, fshape, ncp)
#undef HEAP_COMPARE

		/* Strip out leading zero's */
		for (i = 0; i < ncp; i++) {
			if (fshape[i].we != 0.0)
				break;
		}
		if (i > 1 && i < ncp) {
			memmove(&fshape[0], &fshape[i-1], sizeof(munki_fs) * (ncp - i + 1));
			ncp = ncp - i + 1;
			for (i = 0; i < ncp; i++) {
				if (fshape[i].we != 0.0)
					break;
			}
		}

#ifdef HIGH_RES_PLOT
		/* Plot the shape of the accumulated curve */
		{
			double *x1 = dvectorz(0, ncp-1);
			double *y1 = dvectorz(0, ncp-1);

			for (i = 0; i < ncp; i++) {
				double x;
				x1[i] = fshape[i].wl;
				y1[i] = fshape[i].we;
			}
			if (ref)
				plot_msg("Shape of existing reflection sampling curve:\n");
			else
				plot_msg("Shape of existing emission sampling curve:\n");
			do_plot(x1, y1, NULL, NULL, ncp);

			free_dvector(x1, 0, ncp-1);
			free_dvector(y1, 0, ncp-1);
		}
#endif /* HIGH_RES_PLOT */
	}
#endif /* EXISTING_SHAPE */

#ifdef HIGH_RES_DEBUG
	/* Check that the filter sums to a constant */
	{
		double x, sum;

		for (x = 0.0; x < 10.0; x += 0.2) {
			sum = 0;
			sum += lin_fshape(fshape, ncp, x - 30.0);
			sum += lin_fshape(fshape, ncp, x - 20.0);
			sum += lin_fshape(fshape, ncp, x - 10.0);
			sum += lin_fshape(fshape, ncp, x -  0.0);
			sum += lin_fshape(fshape, ncp, x + 10.0);
			sum += lin_fshape(fshape, ncp, x + 20.0);
			printf("Offset %f, sum %f\n",x, sum);
		}
	}
#endif /* HIGH_RES_DEBUG */

	{
		double fshmax;				/* filter shape max wavelength from center */
#define MXNOWL 200      /* Max hires bands */
#define MXNOFC 32
		munki_fc coeff2[MXNOWL][MXNOFC];	/* New filter cooefficients */
		double twidth;

		/* Construct a set of filters that uses more CCD values */
		twidth = HIGHRES_WIDTH;

		if (m->nwav2 > MXNOWL) {		/* Assert */
			a1logw(p->log,"High res filter has too many bands\n");
			return MUNKI_INT_ASSERT;
		}

#ifdef EXISTING_SHAPE		/* Else generate filter shape */
		/* Cut the filter width by half, to conver from 10nm to 5nm spacing */
		for (i = 0; i < ncp; i++)
			fshape[i].wl *= twidth/10.0;
		fshmax = -fshape[0].wl;		/* aximum extent needed around zero */
		if (fshape[ncp-1].wl > fshmax)
			fshmax = fshape[ncp-1].wl;
#else
		/* Use a crude means of determining width */
		for (fshmax = 50.0; fshmax >= 0.0; fshmax -= 0.1) {
			if (fabs(lanczos2(twidth, fshmax)) > 0.001) {
				fshmax += 0.1;
				break;
			}
		}
		if (fshmax <= 0.0) {
			a1logw(p->log,"munki: fshmax search failed\n");
			return MUNKI_INT_ASSERT;
		}
#endif
//		a1logd(p->log,1,"fshmax = %f\n",fshmax);

#ifdef HIGH_RES_DEBUG
		/* Check that the filter sums to a constant */
		{
			double x, sum;
	
			for (x = 0.0; x < 5.0; x += 0.1) {
				sum = 0;
				sum += lin_fshape(fshape, ncp, x - 15.0);
				sum += lin_fshape(fshape, ncp, x - 10.0);
				sum += lin_fshape(fshape, ncp, x -  5.0);
				sum += lin_fshape(fshape, ncp, x -  0.0);
				sum += lin_fshape(fshape, ncp, x +  5.0);
				sum += lin_fshape(fshape, ncp, x + 10.0);
				printf("Offset %f, sum %f\n",x, sum);
			}
		}
#endif /* HIGH_RES_DEBUG */

		/* Create all the filters */
		if ((*pmtx_nocoef2 = mtx_nocoef2 = (int *)calloc(m->nwav2, sizeof(int))) == NULL) {
			a1logd(p->log,1,"munki: malloc mtx_nocoef2 failed!\n");
			return MUNKI_INT_MALLOC;
		}

		/* For all the useful CCD bands */
		for (i = 0; i < m->nraw; i++) {
			co pp;
			double w1, wl, w2;

			/* Translate CCD center to to wavelength */
			pp.p[0] = (double)i;
			raw2wav->interp(raw2wav, &pp);
			wl = pp.v[0];

			/* Translate CCD cell boundaries index to wavelength */
			pp.p[0] = i - 0.5;
			raw2wav->interp(raw2wav, &pp);
			w1 = pp.v[0];

			pp.p[0] = i + 0.5;
			raw2wav->interp(raw2wav, &pp);
			w2 = pp.v[0];

			a1logd(p->log,1,"CCD %d, wl %f - %f\n",i,w1,w2);

			/* For each filter */
			for (j = 0; j < m->nwav2; j++) {
				double cwl, rwl;		/* center, relative wavelength */
				double we;

				cwl = m->wl_short2 + (double)j * (m->wl_long2 - m->wl_short2)/(m->nwav2-1.0);

				if (cwl < min_wl)		/* Duplicate below this wl */
					cwl = min_wl;

				rwl = wl - cwl;			/* relative wavelgth to filter */

				if (fabs(w1 - cwl) > fshmax && fabs(w2 - cwl) > fshmax)
					continue;		/* Doesn't fall into this filter */

#ifdef BOX_INTEGRATE
				/* Integrate in 0.05 nm increments from filter shape */
				{
					int nn;
					double lw, ll;
#ifdef FAST_HIGH_RES_SETUP
# define FINC 0.2
#else
# define FINC 0.05
#endif
					nn = (int)(fabs(w2 - w1)/FINC + 0.5);
			
					lw = w1;
#ifdef EXISTING_SHAPE
					ll = lin_fshape(fshape, ncp, w1- cwl);
#else
					ll = lanczos2(twidth, w1- cwl);
#endif
					we = 0.0;
					for (k = 0; k < nn; k++) { 
						double cw, cl;

#if defined(__APPLE__) && defined(__POWERPC__)
						gcc_bug_fix(k);
#endif
						cw = w1 + (k+1.0)/(nn +1.0) * (w2 - w1);
#ifdef EXISTING_SHAPE
						cl = lin_fshape(fshape, ncp, cw - cwl);
#else
						cl = lanczos2(twidth, cw- cwl);
#endif
						we += 0.5 * (cl + ll) * (lw - cw);
						ll = cl;
						lw = cw;
					}
				}


#else			/* Point sample with weighting */

#ifdef EXISTING_SHAPE
				we = fabs(w2 - w1) * lin_fshape(fshape, ncp, rwl);
#else
				we = fabs(w2 - w1) * lanczos2(twidth, rwl);
#endif

#endif			/* Integrate/Point sample */

				if (mtx_nocoef2[j] >= MXNOFC) {
					a1logw(p->log,"munki: run out of high res filter space\n");
					return MUNKI_INT_ASSERT;
				}

				coeff2[j][mtx_nocoef2[j]].ix = i;
				coeff2[j][mtx_nocoef2[j]++].we = we; 
				a1logd(p->log,1,"filter %d, cwl %f, rwl %f, ix %d, we %f\n",j,cwl,rwl,i,we);
			}
		}

		/* Dump the filter coefficients */
		if (p->log->debug >= 1) {	

			/* For each output wavelength */
			for (j = 0; j < m->nwav2; j++) {

				a1logd(p->log,1,"filter %d, cwl %f\n",j,XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, j));
				/* For each matrix value */
				for (k = 0; k < mtx_nocoef2[j]; k++) {
					a1logd(p->log,1," CCD %d, we %f\n",coeff2[j][k].ix,coeff2[j][k].we);
				}
			}
		}

#ifdef HIGH_RES_PLOT
		/* Plot resampled curves */
		{
			double *xx, *ss;
			double **yy;

			xx = dvectorz(0, m->nraw-1);		/* X index */
			yy = dmatrixz(0, 5, 0, m->nraw-1);	/* Curves distributed amongst 5 graphs */
			
			for (i = 0; i < m->nraw; i++)
				xx[i] = i;

			/* For each output wavelength */
			for (j = 0; j < m->nwav2; j++) {
				i = j % 5;

				/* For each matrix value */
				for (k = 0; k < mtx_nocoef2[j]; k++) {
					yy[5][coeff2[j][k].ix] += 0.5 * coeff2[j][k].we;
					yy[i][coeff2[j][k].ix] = coeff2[j][k].we;
				}
			}

			if (ref)
				plot_msg("Hi-Res reflection wavelength sampling curves:\n");
			else
				plot_msg("Hi-Res emission wavelength sampling curves:\n");
			do_plot6(xx, yy[0], yy[1], yy[2], yy[3], yy[4], yy[5], m->nraw);
			free_dvector(xx, 0, m->nraw-1);
			free_dmatrix(yy, 0, 2, 0, m->nraw-1);
		}
#endif /* HIGH_RES_PLOT */

		/* Convert into runtime format */
		{
			int xcount;

			if ((*pmtx_index2 = mtx_index2 = (int *)calloc(m->nwav2, sizeof(int))) == NULL) {
				a1logd(p->log,1,"munki: malloc mtx_index2 failed!\n");
				return MUNKI_INT_MALLOC;
			}
	
			xcount = 0;
			for (j = 0; j < m->nwav2; j++) {
				mtx_index2[j] = coeff2[j][0].ix;
				xcount += mtx_nocoef2[j];
			}
	
			if ((*pmtx_coef2 = mtx_coef2 = (double *)calloc(xcount, sizeof(double))) == NULL) {
				a1logd(p->log,1,"munki: malloc mtx_coef2 failed!\n");
				return MUNKI_INT_MALLOC;
			}

			for (i = j = 0; j < m->nwav2; j++)
				for (k = 0; k < mtx_nocoef2[j]; k++, i++)
					mtx_coef2[i] = coeff2[j][k].we;
		}

		/* Normalise the filters area in CCD space, while maintaining the */
		/* total contribution of each CCD at the target too. */
		/* Hmm. This will wreck super-sample. We should fix it */
#ifdef DO_CCDNORM			/* Normalise CCD values to original */
		{
			double x[4], y[4];
			int maxix[2];
			double avg[2], max[2];
			double ccdsum[2][128];			/* Target weight/actual for each CCD */
			double dth[2];

			avg[0] = avg[1] = 0.0;
			max[0] = max[1] = 0.0;
			for (j = 0; j < 128; j++) {
				ccdsum[0][j] = 0.0;
				ccdsum[1][j] = 0.0;
			}

			/* Compute the weighting of each CCD value in the normal output */
			for (cx = j = 0; j < m->nwav1; j++) { /* For each wavelength */

				/* For each matrix value */
				sx = mtx_index1[j];		/* Starting index */
				if (j < (m->nwav1-2)			/* Skip duplicates */
				 && sx == mtx_index1[j+1]
				 && mtx_nocoef1[j] == mtx_nocoef1[j+1]) {
					cx += mtx_nocoef1[j];
//printf("~1 skipping dup Norm CCD [%d] [%d] %f\n",sx,cx, mtx_coef1[cx]);
					continue;			/* Skip all duplicate filters */
				}
				for (k = 0; k < mtx_nocoef1[j]; k++, cx++, sx++) {
					ccdsum[0][sx] += mtx_coef1[cx];
//printf("~1 Norm CCD [%d] %f += [%d] %f\n",sx,ccdsum[0][sx],cx, mtx_coef1[cx]);
				}
			}

			/* Compute the weighting of each CCD value in the hires output */
			for (cx = j = 0; j < m->nwav2; j++) { /* For each wavelength */

				/* For each matrix value */
				sx = mtx_index2[j];		/* Starting index */
				if (j < (m->nwav2-2)			/* Skip duplicates */
				 && sx == mtx_index2[j+1]
				 && mtx_nocoef2[j] == mtx_nocoef2[j+1]) {
					cx += mtx_nocoef2[j];
//printf("~1 skipping dup HiRes CCD [%d] [%d] %f\n",sx,cx, mtx_coef2[cx]);
					continue;			/* Skip all duplicate filters */
				}
				for (k = 0; k < mtx_nocoef2[j]; k++, cx++, sx++) {
					ccdsum[1][sx] += mtx_coef2[cx];
//printf("~1 HiRes CCD [%d] %f += [%d] %f\n",sx,ccdsum[1][sx],cx, mtx_coef2[cx]);
				}
			}

#ifdef HIGH_RES_PLOT
			/* Plot target CCD values */
			{
				double xx[128], y1[128], y2[128];
	
				for (i = 0; i < 128; i++) {
					xx[i] = i;
					y1[i] = ccdsum[0][i];
					y2[i] = ccdsum[1][i];
				}
	
				plot_msg("Raw target and actual CCD weight sums:\n");
				do_plot(xx, y1, y2, NULL, 128);
			}
#endif

			/* Because we're attempting to extend the wl range slightly at both ends, */
			/* we can't really use the normal res ccdsum as the target, since this */
			/* would curtail any extension. So instead we create extrapolated */
			/* ccdsum values at the ends to estimate the needed normalization. */ 
			/* Figure valid range and extrapolate to edges */
			dth[0] = 0.0;		/* ref threshold */
			dth[1] = 0.007;		/* hires threshold */

			for (k = 0; k < 2; k++) {

				/* Find the peak value */
				for (i = 0; i < 64; i++) {
					if (ccdsum[k][i] >= max[k]) {
						max[k] = ccdsum[k][i];
						maxix[k] = i;
					}
				}
				for (i = 127; i >= 0; i--) {
					if (ccdsum[k][i] >= max[k]) {
						max[k] = ccdsum[k][i];
						maxix[k] = i;
					}
				}

//printf("~1 max[%d] = %f at ix %d\n",k, max[k],maxix[k]);
				/* Figure out the valid range */
				for (i = 0; i < maxix[k]; i++) {
					if (ccdsum[k][i] > (0.8 * max[k])) {
						x[0] = (double)i;
						break;
					}
				}
				for (i = 127; i > maxix[k]; i--) {
					if (ccdsum[k][i] > (0.8 * max[k])) {
						x[3] = (double)i;
						break;
					}
				}
//printf("~1 raw extrap nodes %.0f, %.0f\n",x[0],x[3]);
				/* Space off the last couple of entries */
				x[0] += 2.0;
				x[3] -= 6.0;
				x[1] = floor((2 * x[0] + x[3])/3.0);
				x[2] = floor((x[0] + 2 * x[3])/3.0);

				for (i = 0; i < 4; i++)
					y[i] = ccdsum[k][(int)x[i]];

//printf("~1 extrap nodes %f, %f, %f, %f\n",x[0],x[1],x[2],x[3]);
//printf("~1 extrap value %f, %f, %f, %f\n",y[0],y[1],y[2],y[3]);

				for (i = 0; i < 128; i++) {
					double xw, yw;

					xw = (double)i;
	
					/* Compute interpolated value using Lagrange: */
					yw = y[0] * (xw-x[1]) * (xw-x[2]) * (xw-x[3])
						      /((x[0]-x[1]) * (x[0]-x[2]) * (x[0]-x[3]))
					   + y[1] * (xw-x[0]) * (xw-x[2]) * (xw-x[3])
						      /((x[1]-x[0]) * (x[1]-x[2]) * (x[1]-x[3]))
					   + y[2] * (xw-x[0]) * (xw-x[1]) * (xw-x[3])
						      /((x[2]-x[0]) * (x[2]-x[1]) * (x[2]-x[3]))
					   + y[3] * (xw-x[0]) * (xw-x[1]) * (xw-x[2])
						      /((x[3]-x[0]) * (x[3]-x[1]) * (x[3]-x[2]));
	
					if ((xw < x[0] || xw > x[3])
					 && fabs(ccdsum[k][i] - yw)/yw > dth[k]) {
						ccdsum[k][i] = yw;
					}
					avg[k] += ccdsum[k][i];
				}
				avg[k] /= 128.0;
			}

#ifdef HIGH_RES_PLOT
			/* Plot target CCD values */
			{
				double xx[129], y1[129], y2[129];
	
				for (i = 0; i < 128; i++) {
					xx[i] = i;
					y1[i] = ccdsum[0][i]/avg[0];
					y2[i] = ccdsum[1][i]/avg[1];
				}
				xx[i] = i;
				y1[i] = 0.0;
				y2[i] = 0.0;
	
				plot_msg("Extrap. target and actual CCD weight sums:\n");
				do_plot(xx, y1, y2, NULL, 129);
			}
#endif

#ifdef DO_CCDNORMAVG	/* Just correct by average */
			for (cx = j = 0; j < m->nwav2; j++) { /* For each wavelength */

				/* For each matrix value */
				sx = mtx_index2[j];		/* Starting index */
				for (k = 0; k < mtx_nocoef2[j]; k++, cx++, sx++) {
					mtx_coef2[cx] *= 10.0/twidth * avg[0]/avg[1];
				}
			}

#else		/* Correct by CCD bin */

			/* Correct the weighting of each CCD value in the hires output */
			for (i = 0; i < 128; i++) {
				ccdsum[1][i] = 10.0/twidth * ccdsum[0][i]/ccdsum[1][i];		/* Correction factor */
			}
			for (cx = j = 0; j < m->nwav2; j++) { /* For each wavelength */

				/* For each matrix value */
				sx = mtx_index2[j];		/* Starting index */
				for (k = 0; k < mtx_nocoef2[j]; k++, cx++, sx++) {
					mtx_coef2[cx] *= ccdsum[1][sx];
				}
			}
#endif
		}
#endif /* DO_CCDNORM */

#ifdef HIGH_RES_PLOT
		{
			static munki_fc coeff2[MXNOWL][MXNOFC];	
			double *xx, *ss;
			double **yy;

			/* Convert the native filter cooeficient representation to */
			/* a 2D array we can randomly index. */
			for (cx = j = 0; j < m->nwav2; j++) { /* For each output wavelength */
				if (j >= MXNOWL) {	/* Assert */
					a1loge(p->log,1,"munki: number of hires output wavelenths is > %d\n",MXNOWL);
					return MUNKI_INT_ASSERT;
				}

				/* For each matrix value */
				sx = mtx_index2[j];		/* Starting index */
				for (k = 0; k < mtx_nocoef2[j]; k++, cx++, sx++) {
					if (k >= MXNOFC) {	/* Assert */
						a1loge(p->log,1,"munki: number of hires filter coeefs is > %d\n",MXNOFC);
						return MUNKI_INT_ASSERT;
					}
					coeff2[j][k].ix = sx;
					coeff2[j][k].we = mtx_coef2[cx];
				}
			}
	
			xx = dvectorz(-1, m->nraw-1);		/* X index */
			yy = dmatrixz(0, 5, -1, m->nraw-1);	/* Curves distributed amongst 5 graphs */
			
			for (i = 0; i < m->nraw; i++)
				xx[i] = i;

			/* For each output wavelength */
			for (j = 0; j < m->nwav2; j++) {
				i = j % 5;

				/* For each matrix value */
				for (k = 0; k < mtx_nocoef2[j]; k++) {
					yy[5][coeff2[j][k].ix] += 0.5 * coeff2[j][k].we;
					yy[i][coeff2[j][k].ix] = coeff2[j][k].we;
				}
			}

			plot_msg("Normalized Hi-Res wavelength sampling curves: %s\n",ref ? "refl" : "emis");
			do_plot6(xx, yy[0], yy[1], yy[2], yy[3], yy[4], yy[5], m->nraw);
			free_dvector(xx, -1, m->nraw-1);
			free_dmatrix(yy, 0, 2, -1, m->nraw-1);
		}
#endif /* HIGH_RES_PLOT */
#undef MXNOWL
#undef MXNOFC

		/* Basic capability is initialised */
		m->hr_inited++;

		/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
		/* If both reflective and emissive samplings have been created, */
		/* deal with upsampling the references and calibrations */
		if (m->hr_inited == 2) {
#ifdef ONEDSTRAYLIGHTUS
			double **slp;			/* 2D Array of stray light values */
#endif /* !ONEDSTRAYLIGHTUS */
			rspl *trspl;			/* Upsample rspl */
			cow sd[40 * 40];		/* Scattered data points of existing references */
			datai glow, ghigh;
			datao vlow, vhigh;
			int gres[2];
			double avgdev[2];
			int ii, jj;
			co pp;
	
			/* First the 1D references */
			if ((trspl = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) {
				a1logd(p->log,3,"munki: creating rspl for high res conversion failed\n");
				raw2wav->del(raw2wav);
				return MUNKI_INT_NEW_RSPL_FAILED;
			}
			
			for (ii = 0; ii < 4; ii++) {
				double **ref2, *ref1;

				if (ii == 0) {
					ref1 = m->white_ref1;
					ref2 = &m->white_ref2;
				} else if (ii == 1) {
					ref1 = m->emis_coef1;
					ref2 = &m->emis_coef2;
				} else if (ii == 2) {
					ref1 = m->amb_coef1;
					ref2 = &m->amb_coef2;
				} else {
					ref1 = m->proj_coef1;
					ref2 = &m->proj_coef2;
				}

				vlow[0] = 1e6;
				vhigh[0] = -1e6;

				/* Set scattered points */
				for (i = 0; i < m->nwav1; i++) {

					sd[i].p[0] = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, i);
					sd[i].v[0] = ref1[i];
					sd[i].w = 1.0;

					if (sd[i].v[0] < vlow[0])
						vlow[0] = sd[i].v[0];
					if (sd[i].v[0] > vhigh[0])
						vhigh[0] = sd[i].v[0];
				}

#ifdef NEVER
				/* Add some corrections at short wavelengths */
				/* (The combination of the diffraction grating and */
				/*  LED light source doesn't give us much to work with here.) */
				if (ii == 1) {	/* Emission */
					sd[0].v[0] *= 10.0;		/* 380 */
					sd[1].v[0] *= 3.0;		/* 390 */
					sd[2].v[0] *= 1.0;		/* 400 */
				}

				if (ii == 2) {	/* Ambient */
					sd[0].v[0] *= 5.0;		/* 380 */		/* Doesn't help much, because */
					sd[1].v[0] *= 2.0;		/* 390 */		/* the diffuser absorbs short WL */
					sd[2].v[0] *= 1.0;		/* 400 */
				}

				if (ii == 3) {	/* Projector */
					sd[0].v[0] *= 0.1;		/* 380 */
					sd[1].v[0] *= 1.0;		/* 390 */

					sd[i].p[0] = 350.0; 	/* 350 */
					sd[i].v[0] = 0.2 * sd[0].v[0];
					sd[i++].w = 1.0;
				}
#endif

				glow[0] = m->wl_short2;
				ghigh[0] = m->wl_long2;
				gres[0] = m->nwav2;
				avgdev[0] = 0.0;
				
				trspl->fit_rspl_w(trspl, 0, sd, i, glow, ghigh, gres, vlow, vhigh, 5.0, avgdev, NULL);

				if ((*ref2 = (double *)calloc(m->nwav2, sizeof(double))) == NULL) {
					raw2wav->del(raw2wav);
					trspl->del(trspl);
					a1logd(p->log,1,"munki: malloc mtx_coef2 failed!\n");
					return MUNKI_INT_MALLOC;
				}

				/* Create upsampled version */
				for (i = 0; i < m->nwav2; i++) {
					pp.p[0] = XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, i);
					if (pp.p[0] < min_wl)	/* Duplicate below this wl */
						pp.p[0] = min_wl;
					trspl->interp(trspl, &pp);
					if (pp.v[0] < 0.0)
						pp.v[0] = 0.0;
					(*ref2)[i] = pp.v[0];
				}


#ifdef NEVER
				/* Add some corrections at short wavelengths */
				if (ii == 0) {
					/* 376.67 - 470 */
					double corr[5][29] = {
						{ 4.2413, 4.0654, 3.6425, 3.2194, 2.8692, 2.3964,
						  1.9678, 1.3527, 0.7978, 0.7823, 0.8474, 0.9227,
						   0.9833, 1.0164, 1.0270, 1.0241, 1.0157, 1.0096,
						   1.0060, 1.0, 1.0, 1.0, 1.0, 1.0,
						   1.0, 1.0, 1.0, 1.0, 1.0 },

					};

					for (i = 0; i < m->nwav2; i++) {
						double wl;
						int ix;
						wl = XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, i);
						ix = XSPECT_IX(376.6666667, 470.0, 29, wl);

						if (ix < 0)
							ix = 0;
						else if (ix >= 29)
							ix = 28;
						(*ref2)[i] *= corr[ii][ix];
					}

				}
#endif

#ifdef HIGH_RES_PLOT
				/* Plot original and upsampled reference */
				{
					double *x1 = dvectorz(0, m->nwav2-1);
					double *y1 = dvectorz(0, m->nwav2-1);
					double *y2 = dvectorz(0, m->nwav2-1);
		
					for (i = 0; i < m->nwav2; i++) {
						double wl = m->wl_short2 + (double)i * (m->wl_long2 - m->wl_short2)/(m->nwav2-1.0);
						x1[i] = wl;
						y1[i] = (*ref2)[i];
						if (wl < m->wl_short1 || wl > m->wl_long1) {
							y2[i] = 0.0;
						} else {
							double x, wl1, wl2;
							for (j = 0; j < (m->nwav1-1); j++) {
								wl1 = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, j);
								wl2 = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, j+1);
								if (wl >= wl1 && wl <= wl2)
									break;
							}
							x = (wl - wl1)/(wl2 - wl1);
							y2[i] = ref1[j] + (ref1[j+1] - ref1[j]) * x;
						}
					}
					printf("Original and up-sampled ");
					if (ii == 0) {
						plot_msg("Reflective cal. curve:\n");
					} else if (ii == 1) {
						plot_msg("Emission cal. curve:\n");
					} else if (ii == 2) {
						plot_msg("Ambient cal. curve:\n");
					} else {
						plot_msg("Projector cal. curve:\n");
					}
					do_plot(x1, y1, y2, NULL, m->nwav2);
		
					free_dvector(x1, 0, m->nwav2-1);
					free_dvector(y1, 0, m->nwav2-1);
					free_dvector(y2, 0, m->nwav2-1);
				}
#endif /* HIGH_RES_PLOT */
			}
			trspl->del(trspl);

#ifdef ONEDSTRAYLIGHTUS
			/* Then the 2D stray light using linear interpolation */
			slp = dmatrix(0, m->nwav1-1, 0, m->nwav1-1);

			/* Set scattered points */
			for (i = 0; i < m->nwav1; i++) {		/* Output wavelength */
				for (j = 0; j < m->nwav1; j++) {	/* Input wavelength */

					slp[i][j] = m->straylight1[i][j];

					/* Use interpolate/extrapolate for middle points */
					if (j == (i-1) || j == i || j == (i+1)) {
						int j0, j1;
						double w0, w1;
						if (j == (i-1)) {
							if (j <= 0)
								j0 = j+3, j1 = j+4;
							else if (j >= (m->nwav1-3))
								j0 = j-2, j1 = j-1;
							else
								j0 = j-1, j1 = j+3;
						} else if (j == i) {
							if (j <= 1)
								j0 = j+2, j1 = j+3;
							else if (j >= (m->nwav1-2))
								j0 = j-3, j1 = j-2;
							else
								j0 = j-2, j1 = j+2;
						} else if (j == (i+1)) {
							if (j <= 2)
								j0 = j+1, j1 = j+2;
							else if (j >= (m->nwav1-1))
								j0 = j-4, j1 = j-3;
							else
								j0 = j-3, j1 = j+1;
						}
						w1 = (j - j0)/(j1 - j0);
						w0 = 1.0 - w1;
						slp[i][j] = w0 * m->straylight1[i][j0]
						          + w1 * m->straylight1[i][j1];

					}
				}
			}
#else	/* !ONEDSTRAYLIGHTUS */
			/* Then setup 2D stray light using rspl */
			if ((trspl = new_rspl(RSPL_NOFLAGS, 2, 1)) == NULL) {
				a1logd(p->log,3,"munki: creating rspl for high res conversion failed\n");
				raw2wav->del(raw2wav);
				return MUNKI_INT_NEW_RSPL_FAILED;
			}
			
			/* Set scattered points */
			for (i = 0; i < m->nwav1; i++) {		/* Output wavelength */
				for (j = 0; j < m->nwav1; j++) {	/* Input wavelength */
					int ix = i * m->nwav1 + j;

					sd[ix].p[0] = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, i);
					sd[ix].p[1] = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, j);
					sd[ix].v[0] = m->straylight1[i][j];
					sd[ix].w = 1.0;
					if (j == (i-1) || j == i || j == (i+1))
						sd[ix].w = 0.0;
				}
			}

			glow[0] = m->wl_short2;
			glow[1] = m->wl_short2;
			ghigh[0] = m->wl_long2;
			ghigh[1] = m->wl_long2;
			gres[0] = m->nwav2;
			gres[1] = m->nwav2;
			avgdev[0] = 0.0;
			avgdev[1] = 0.0;
			
			trspl->fit_rspl_w(trspl, 0, sd, m->nwav1 * m->nwav1, glow, ghigh, gres, NULL, NULL, 0.5, avgdev, NULL);
#endif	/* !ONEDSTRAYLIGHTUS */

			m->straylight2 = dmatrixz(0, m->nwav2-1, 0, m->nwav2-1);  

			/* Create upsampled version */
			for (i = 0; i < m->nwav2; i++) {		/* Output wavelength */
				for (j = 0; j < m->nwav2; j++) {	/* Input wavelength */
					double p0, p1;
					p0 = XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, i);
					if (p0 < min_wl)	/* Duplicate below this wl */
						p0 = min_wl;
					p1 = XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, j);
					if (p1 < min_wl)	/* Duplicate below this wl */
						p1 = min_wl;
#ifdef ONEDSTRAYLIGHTUS
					/* Do linear interp with clipping at ends */
					{
						int x0, x1, y0, y1;
						double xx, yy, w0, w1, v0, v1;

						xx = (m->nwav1-1.0) * (p0 - m->wl_short1)/(m->wl_long1 - m->wl_short1);
						x0 = (int)floor(xx);
						if (x0 <= 0)
							x0 = 0;
						else if (x0 >= (m->nwav1-2))
							x0 = m->nwav1-2;
						x1 = x0 + 1;
						w1 = xx - (double)x0;
						w0 = 1.0 - w1;

						yy = (m->nwav1-1.0) * (p1 - m->wl_short1)/(m->wl_long1 - m->wl_short1);
						y0 = (int)floor(yy);
						if (y0 <= 0)
							y0 = 0;
						else if (y0 >= (m->nwav1-2))
							y0 = m->nwav1-2;
						y1 = y0 + 1;
						v1 = yy - (double)y0;
						v0 = 1.0 - v1;

						pp.v[0] = w0 * v0 * slp[x0][y0] 
						        + w0 * v1 * slp[x0][y1] 
						        + w1 * v0 * slp[x1][y0] 
						        + w1 * v1 * slp[x1][y1]; 
					}
#else /* !ONEDSTRAYLIGHTUS */
					pp.p[0] = p0;
					pp.p[1] = p1;
					trspl->interp(trspl, &pp);
#endif /* !ONEDSTRAYLIGHTUS */
					m->straylight2[i][j] = pp.v[0] * HIGHRES_WIDTH/10.0;
					if (m->straylight2[i][j] > 0.0)
						m->straylight2[i][j] = 0.0;
				}
			}

			/* Fix primary wavelength weight and neighbors */
			for (i = 0; i < m->nwav2; i++) {		/* Output wavelength */
				double sum;

				if (i > 0)
					m->straylight2[i][i-1] = 0.0;
				m->straylight2[i][i] = 0.0;
				if (i < (m->nwav2-1))
					m->straylight2[i][i+1] = 0.0;

				for (sum = 0.0, j = 0; j < m->nwav2; j++)
					sum += m->straylight2[i][j];

				m->straylight2[i][i] = 1.0 - sum;		/* Total sum should be 1.0 */
			}

#ifdef HIGH_RES_PLOT_STRAYL
			/* Plot original and upsampled reference */
			{
				double *x1 = dvectorz(0, m->nwav2-1);
				double *y1 = dvectorz(0, m->nwav2-1);
				double *y2 = dvectorz(0, m->nwav2-1);
	
				for (i = 0; i < m->nwav2; i++) {		/* Output wavelength */
					double wli = XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, i);
					int i1 = XSPECT_IX(m->wl_short1, m->wl_long1, m->nwav1, wli);

					for (j = 0; j < m->nwav2; j++) {
						double wl = XSPECT_WL(m->wl_short2, m->wl_long2, m->nwav2, j);
						x1[j] = wl;
						y1[j] = m->straylight2[i][j];
						if (y1[j] == 0.0)
							y1[j] = -8.0;
						else
							y1[j] = log10(fabs(y1[j]));
						if (wli < m->wl_short1 || wli > m->wl_long1
						 || wl < m->wl_short1 || wl > m->wl_long1) {
							y2[j] = -8.0;
						} else {
							double x, wl1, wl2;
							for (k = 0; k < (m->nwav1-1); k++) {
								wl1 = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, k);
								wl2 = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, k+1);
								if (wl >= wl1 && wl <= wl2)
									break;
							}
							x = (wl - wl1)/(wl2 - wl1);
							y2[j] = m->straylight1[i1][k] + (m->straylight1[i1][k+1]
							      - m->straylight1[i1][k]) * x;
							if (y2[j] == 0.0)
								y2[j] = -8.0;
							else
								y2[j] = log10(fabs(y2[j]));
						}
					}
					do_plot(x1, y1, y2, NULL, m->nwav2);
				}
	
				free_dvector(x1, 0, m->nwav2-1);
				free_dvector(y1, 0, m->nwav2-1);
				free_dvector(y2, 0, m->nwav2-1);
			}
#endif /* HIGH_RES_PLOT */

#ifdef ONEDSTRAYLIGHTUS
			free_dmatrix(slp, 0, m->nwav1-1, 0, m->nwav1-1);
#else /* !ONEDSTRAYLIGHTUS */
			trspl->del(trspl);
#endif /* !ONEDSTRAYLIGHTUS */

			/* - - - - - - - - - - - - - - - - - - - - - - - - - */
			/* Allocate space for per mode calibration reference */
			/* and bring high res calibration factors into line */
			/* with current standard res. ones */
			for (i = 0; i < mk_no_modes; i++) {
				munki_state *s = &m->ms[i];

				s->cal_factor2 = dvectorz(0, m->nwav2-1);

				switch(i) {
					case mk_refl_spot:
					case mk_refl_scan:
						if (s->cal_valid) {
							munki_absraw_to_abswav1(p, 1, &s->cal_factor1, &s->white_data);
							munki_absraw_to_abswav2(p, 1, &s->cal_factor2, &s->white_data);
							munki_compute_white_cal(p, s->cal_factor1, m->white_ref1, s->cal_factor1,
							                           s->cal_factor2, m->white_ref2, s->cal_factor2);
						}
						break;

					case mk_emiss_spot_na:
					case mk_emiss_spot:
					case mk_emiss_scan:
						for (j = 0; j < m->nwav2; j++)
							s->cal_factor2[j] = EMIS_SCALE_FACTOR * m->emis_coef2[j];
						break;
		
					case mk_tele_spot_na:
					case mk_tele_spot:
						for (j = 0; j < m->nwav2; j++)
							s->cal_factor2[j] = EMIS_SCALE_FACTOR * m->proj_coef2[j];
						break;
		
					case mk_amb_spot:
					case mk_amb_flash:
						if (m->amb_coef1 != NULL) {
							for (j = 0; j < m->nwav2; j++)
								s->cal_factor2[j] = AMB_SCALE_FACTOR * m->amb_coef2[j];
							s->cal_valid = 1;
						}
						break;
					case mk_trans_spot:
					case mk_trans_scan:
						if (s->cal_valid) {
							munki_absraw_to_abswav1(p, 1, &s->cal_factor1, &s->white_data);
							munki_absraw_to_abswav2(p, 1, &s->cal_factor2, &s->white_data);
							munki_compute_white_cal(p, s->cal_factor1, NULL, s->cal_factor1,
				                                       s->cal_factor2, NULL, s->cal_factor2);
						}
						break;
				}
			}
		}
	}

	raw2wav->del(raw2wav);

	return ev;
}

#endif /* HIGH_RES */


/* return nz if high res is supported */
int munki_imp_highres(munki *p) {
#ifdef HIGH_RES
	return 1;
#else
	return 0;
#endif /* HIGH_RES */
}

/* Set to high resolution mode */
munki_code munki_set_highres(munki *p) {
	int i;
	munkiimp *m = (munkiimp *)p->m;
	munki_code ev = MUNKI_OK;

#ifdef HIGH_RES
	if (m->hr_inited == 0) {
		if ((ev = munki_create_hr(p, 1)) != MUNKI_OK)	/* Reflective */
			return ev;	
		if ((ev = munki_create_hr(p, 0)) != MUNKI_OK)	/* Emissive */
			return ev;
	}

	m->nwav = m->nwav2;
	m->wl_short = m->wl_short2;
	m->wl_long = m->wl_long2; 

	m->rmtx_index = m->rmtx_index2;
	m->rmtx_nocoef = m->rmtx_nocoef2;
	m->rmtx_coef = m->rmtx_coef2;
	m->emtx_index = m->emtx_index2;
	m->emtx_nocoef = m->emtx_nocoef2;
	m->emtx_coef = m->emtx_coef2;
	m->white_ref = m->white_ref2;
	m->emis_coef = m->emis_coef2;
	m->amb_coef = m->amb_coef2;
	m->proj_coef = m->proj_coef2;
	m->straylight = m->straylight2;

	for (i = 0; i < mk_no_modes; i++) {
		munki_state *s = &m->ms[i];
		s->cal_factor = s->cal_factor2;
	}
	m->highres = 1;
#else
	ev = MUNKI_UNSUPPORTED;
#endif /* HIGH_RES */

	return ev;
}

/* Set to standard resolution mode */
munki_code munki_set_stdres(munki *p) {
	int i;
	munkiimp *m = (munkiimp *)p->m;
	munki_code ev = MUNKI_OK;

#ifdef HIGH_RES
	m->nwav = m->nwav1;
	m->wl_short = m->wl_short1;
	m->wl_long = m->wl_long1; 

	m->rmtx_index = m->rmtx_index1;
	m->rmtx_nocoef = m->rmtx_nocoef1;
	m->rmtx_coef = m->rmtx_coef1;
	m->emtx_index = m->emtx_index1;
	m->emtx_nocoef = m->emtx_nocoef1;
	m->emtx_coef = m->emtx_coef1;
	m->white_ref = m->white_ref1;
	m->emis_coef = m->emis_coef1;
	m->amb_coef = m->amb_coef1;
	m->proj_coef = m->proj_coef1;
	m->straylight = m->straylight1;

	for (i = 0; i < mk_no_modes; i++) {
		munki_state *s = &m->ms[i];
		s->cal_factor = s->cal_factor1;
	}
	m->highres = 0;

#else
	ev = MUNKI_UNSUPPORTED;
#endif /* HIGH_RES */

	return ev;
}

/* Modify the scan consistency tolerance */
munki_code munki_set_scan_toll(munki *p, double toll_ratio) {
	munkiimp *m = (munkiimp *)p->m;
	munki_code ev = MUNKI_OK;

	m->scan_toll_ratio = toll_ratio;

	return MUNKI_OK;
}

/* Optical adjustment weights */
static double opt_adj_weights[21] = {
	1.4944496665144658e-282, 2.0036175483913455e-070, 1.2554893022685038e+232,
	2.3898157055642966e+190, 1.5697625128432372e-076, 6.6912978722191457e+281,
	1.2369092402930559e+277, 1.4430907501246712e-153, 3.0017439193018232e+238,
	1.2978311824382444e+161, 5.5068703318775818e-311, 7.7791723264455314e-260,
	6.4560484084110176e+170, 8.9481529920968425e+165, 1.3565405878488529e-153,
	2.0835868791190880e-076, 5.4310198502711138e+241, 4.8689849775675438e+275,
	9.2709981544886391e+122, 3.7958270103353899e-153, 7.1366083837501666e-154
};

/* Convert from spectral to XYZ, and transfer to the ipatch array */
munki_code munki_conv2XYZ(
	munki *p,
	ipatch *vals,		/* Values to return */
	int nvals,			/* Number of values */
	double **specrd,	/* Spectral readings */
	instClamping clamp	/* Clamp XYZ/Lab to be +ve */
) {
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	xsp2cie *conv;			/* Spectral to XYZ conversion object */
	int i, j, k;
	int six = 0;			/* Starting index */
	int nwl = m->nwav;		/* Number of wavelengths */
	double wl_short = m->wl_short;	/* Starting wavelength */
	volatile double sms;	/* Weighting ("volatile" thanks to mips compiler) */

	if (s->emiss)
		conv = new_xsp2cie(icxIT_none, 0.0, NULL, icxOT_CIE_1931_2, NULL, icSigXYZData, (icxClamping)clamp);
	else
		conv = new_xsp2cie(icxIT_D50, 0.0, NULL, icxOT_CIE_1931_2, NULL, icSigXYZData, (icxClamping)clamp);
	if (conv == NULL)
		return MUNKI_INT_CIECONVFAIL;

	a1logd(p->log,3,"munki_conv2XYZ got wl_short %f, wl_long %f, nwav %d\n"
	                "      after skip got wl_short %f, nwl = %d\n",
	                m->wl_short, m->wl_long, m->nwav, wl_short, nwl);

	for (sms = 0.0, i = 1; i < 21; i++)
		sms += opt_adj_weights[i];
	sms *= opt_adj_weights[0];

	for (i = 0; i < nvals; i++) {

		vals[i].loc[0] = '\000';
		vals[i].mtype = inst_mrt_none;
		vals[i].mcond = inst_mrc_none;
		vals[i].XYZ_v = 0;
		vals[i].sp.spec_n = 0;
		vals[i].duration = 0.0;
	
		vals[i].sp.spec_n = nwl;
		vals[i].sp.spec_wl_short = wl_short;
		vals[i].sp.spec_wl_long = m->wl_long;

		if (s->emiss) {
			for (j = six, k = 0; j < m->nwav; j++, k++) {
				vals[i].sp.spec[k] = specrd[i][j] * sms;
			}
			vals[i].sp.norm = 1.0;

			/* Set the XYZ */
			conv->convert(conv, vals[i].XYZ, &vals[i].sp);
			vals[i].XYZ_v = 1;

			if (s->ambient) {
				if (s->flash)
					vals[i].mtype = inst_mrt_ambient_flash;
				else
					vals[i].mtype = inst_mrt_ambient;
			} else {
				if (s->flash)
					vals[i].mtype = inst_mrt_emission_flash;
				else
					vals[i].mtype = inst_mrt_emission;
			}

		} else {
			for (j = six, k = 0; j < m->nwav; j++, k++) {
				vals[i].sp.spec[k] = 100.0 * specrd[i][j] * sms;
			}
			vals[i].sp.norm = 100.0;

			/* Set the XYZ */
			conv->convert(conv, vals[i].XYZ, &vals[i].sp);
			vals[i].XYZ_v = 1;
			vals[i].XYZ[0] *= 100.0;
			vals[i].XYZ[1] *= 100.0;
			vals[i].XYZ[2] *= 100.0;

			if (s->trans)
				vals[i].mtype = inst_mrt_transmissive;
			else {
				vals[i].mtype = inst_mrt_reflective;
				vals[i].mcond = inst_mrc_uvcut;
			}
		}

		/* Don't return spectral if not asked for */
		if (!m->spec_en) {
			vals[i].sp.spec_n = 0;
		}
		a1logd(p->log,3,"munki_conv2XYZ returning XYZi[%d] %f %f %f\n",
		              i, vals[i].XYZ[0], vals[i].XYZ[1], vals[i].XYZ[2]);
	}

	conv->del(conv);

	/* Apply any XRGA conversion */
	ipatch_convert_xrga(vals, nvals, xcalstd_nonpol, m->target_calstd, m->native_calstd, clamp);

	/* Apply custom filter compensation */
	if (m->custfilt_en)
		ipatch_convert_custom_filter(vals, nvals, &m->custfilt, clamp);


	return MUNKI_OK;
}

/* Compute a mode calibration factor given the reading of the white reference. */
/* Return 1 if any of the transmission wavelengths are low. */
int munki_compute_white_cal(
	munki *p,
	double *cal_factor1,	/* [nwav1] Calibration factor to compute */
	double *white_ref1,		/* [nwav1] White reference to aim for, NULL for 1.0 */
	double *white_read1,	/* [nwav1] The white that was read */
	double *cal_factor2,	/* [nwav2] Calibration factor to compute */
	double *white_ref2,		/* [nwav2] White reference to aim for, NULL for 1.0 */
	double *white_read2		/* [nwav2] The white that was read */
) {
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	int j, warn = 0;
	
	a1logd(p->log,3,"munki_compute_white_cal called\n");

	if (white_ref1 == NULL) {		/* transmission white reference */
		double avgwh = 0.0;

		/* Compute average white reference reading */
		for (j = 0; j < m->nwav1; j++)
			avgwh += white_read1[j];
		avgwh /= (double)m->nwav1;

		/* For each wavelength */
		for (j = 0; j < m->nwav1; j++) {
			/* If reference is < 0.4% of average */
			if (white_read1[j]/avgwh < 0.004) {
				cal_factor1[j] = 1.0/(0.004 * avgwh);
				warn = 1;
			} else {
				cal_factor1[j] = 1.0/white_read1[j];	
			}
		}

	} else {					/* Reflection white reference */

		/* For each wavelength */
		for (j = 0; j < m->nwav1; j++) {
			if (white_read1[j] < 1000.0)
				cal_factor1[j] = white_ref1[j]/1000.0;	
			else
				cal_factor1[j] = white_ref1[j]/white_read1[j];	
		}
	}

#ifdef HIGH_RES
	if (m->hr_inited == 0)
		return warn;

	if (white_ref2 == NULL) {		/* transmission white reference */
		double avgwh = 0.0;

		/* Compute average white reference reading */
		for (j = 0; j < m->nwav2; j++)
			avgwh += white_read2[j];
		avgwh /= (double)m->nwav2;

		/* For each wavelength */
		for (j = 0; j < m->nwav2; j++) {
			/* If reference is < 0.4% of average */
			if (white_read2[j]/avgwh < 0.004) {
				cal_factor2[j] = 1.0/(0.004 * avgwh);
				warn = 1;
			} else {
				cal_factor2[j] = 1.0/white_read2[j];	
			}
		}

	} else {					/* Reflection white reference */

		/* For each wavelength */
		for (j = 0; j < m->nwav2; j++) {
			if (white_read2[j] < 1000.0)
				cal_factor2[j] = white_ref2[j]/1000.0;	
			else
				cal_factor2[j] = white_ref2[j]/white_read2[j];	
		}
	}
#endif /* HIGH_RES */
	return warn;
}

/* For adaptive mode, compute a new integration time and gain mode */
/* in order to optimise the sensor values. */
munki_code munki_optimise_sensor(
	munki *p,
	double *pnew_int_time,
	int    *pnew_gain_mode,
	double cur_int_time,	/* Current intergration time */
	int    cur_gain_mode,	/* nz if currently high gain */
	int    permithg,		/* nz to permit switching to high gain mode */
	int    permitclip,		/* nz to permit clipping out of range int_time, else error */
	double *targoscale,		/* Optimising target scale ( <= 1.0) */
							/* (May be altered if integration time isn't possible) */
	double scale,			/* scale needed of current int time to reach optimum */
	double deadtime			/* Dead integration time (if any) */
) {
	munki_code ev = MUNKI_OK;
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	double new_int_time;
	double min_int_time;	/* Adjusted min_int_time */
	int    new_gain_mode;

	a1logd(p->log,3,"munki_optimise_sensor called, inttime %f, gain mode %d, scale %f\n",cur_int_time,cur_gain_mode, scale);

	min_int_time = m->min_int_time - deadtime;
	cur_int_time -= deadtime;

	/* Compute new normal gain integration time */
	if (cur_gain_mode)
		new_int_time = cur_int_time * scale * m->highgain;
	else
		new_int_time = cur_int_time * scale;
	new_gain_mode = 0;

	a1logd(p->log,3,"target inttime %f, gain mode %d\n",new_int_time,new_gain_mode);

	/* Adjust to low light situation by increasing the integration time. */
	if (new_int_time > s->targmaxitime) {	/* Exceeding target integration time */
		if (s->targmaxitime/new_int_time > s->targoscale2) {	/* But within range */
			/* Compromise sensor target value to maintain targmaxitime */
			new_int_time = s->targmaxitime;
			a1logd(p->log,3,"Using targmaxitime with compromise sensor target\n");
		} else {
			/* Target reduced sensor value to give improved measurement time and continuity */
			new_int_time *= s->targoscale2;
			a1logd(p->log,3,"Using compromse sensor target\n");
		}
		/* Hmm. It seems not be a good idea to use high gain mode if it compromises */
		/* the longer integration time which reduces noise. */
		if (s->auto_gain) {
			if (new_int_time > m->max_int_time && permithg) {
				new_int_time /= m->highgain;
				new_gain_mode = 1;
				a1logd(p->log,3,"Switching to high gain mode\n");
			}
		}
	}
	a1logd(p->log,3,"after low light adjust, inttime %f, gain mode %d\n",new_int_time,new_gain_mode);

	/* Deal with still low light */
	if (new_int_time > m->max_int_time) {
		if (permitclip)
			new_int_time = m->max_int_time;
		else
			return MUNKI_RD_LIGHTTOOLOW;
	}
	a1logd(p->log,3,"after low light clip, inttime %f, gain mode %d\n",new_int_time,new_gain_mode);

	/* Adjust to high light situation */
	if (new_int_time < min_int_time && *targoscale < 1.0) {
		*targoscale *= min_int_time/new_int_time;
		new_int_time = min_int_time;
	}
	a1logd(p->log,3,"after high light adjust, targoscale %f, inttime %f, gain mode %d\n",*targoscale, new_int_time,new_gain_mode);

	/* Deal with still high light */
	if (new_int_time < min_int_time) {
		if (permitclip)
			new_int_time = min_int_time;
		else
			return MUNKI_RD_LIGHTTOOHIGH;
	}
	a1logd(p->log,3,"after high light clip, returning inttime %f, gain mode %d\n",new_int_time,new_gain_mode);

	new_int_time += deadtime;

	a1logd(p->log,3,"munki_optimise_sensor returning inttime %f, gain mode %d\n",new_int_time,new_gain_mode);
	if (pnew_int_time != NULL)
		*pnew_int_time = new_int_time;

	if (pnew_gain_mode != NULL)
		*pnew_gain_mode = new_gain_mode;

	return MUNKI_OK;
}

/* Compute the number of measurements needed, given the target */
/* time and integration time. Will return 0 if target time is 0 */
int munki_comp_nummeas(
	munki *p,
	double meas_time,
	double int_time
) {
	int nmeas;
	if (meas_time <= 0.0)
		return 0;
	nmeas = (int)floor(meas_time/int_time + 0.5);
	if (nmeas < 1)
		nmeas = 1;
	return nmeas;
}

/* Compute the rounded up number of measurements needed, */
/* given the target time and integration time. */
/* Will return 0 if target time is 0 */
int munki_comp_ru_nummeas(
	munki *p,
	double meas_time,
	double int_time
) {
	int nmeas;
	if (meas_time <= 0.0)
		return 0;
	nmeas = (int)ceil(meas_time/int_time);
	return nmeas;
}

/* Convert the dark interpolation data to a useful state */
/* (also allow for interpolating the shielded cell values) */
void
munki_prepare_idark(
	munki *p
) {
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	int i, j;

	/* For normal and high gain */
	for (i = 0; i < 4; i+=2) {
		for (j = -1; j < m->nraw; j++) {
			double d01, d1;
			d01 = s->idark_data[i+0][j];
			d1 = s->idark_data[i+1][j];
	
			/* Compute increment proportional to time */
			s->idark_data[i+1][j] = (d1 - d01)/(s->idark_int_time[i+1] - s->idark_int_time[i+0]);
	
			/* Compute base */
			s->idark_data[i+0][j] = d01 - s->idark_data[i+1][j] * s->idark_int_time[i+0];;
		}
	}
}

/* Create the dark reference for the given integration time and gain */
/* by interpolating from the 4 readings prepared earlier */
munki_code
munki_interp_dark(
	munki *p,
	double *result,		/* Put result of interpolation here */
	double inttime,
	int gainmode
) {
	munkiimp *m = (munkiimp *)p->m;
	munki_state *s = &m->ms[m->mmode];
	int i, j;

	if (!s->idark_valid)
		return MUNKI_INT_NOTCALIBRATED;
		
	i = 0;
	if (s->auto_gain && gainmode)
		i = 2;

	for (j = -1; j < m->nraw; j++) {
		double tt;
		tt = s->idark_data[i+0][j] + inttime * s->idark_data[i+1][j];
		result[j] = tt;
	}
	return MUNKI_OK;
}

/* Set the noinitcalib mode */
void munki_set_noinitcalib(munki *p, int v, int losecs) {
	munkiimp *m = (munkiimp *)p->m;
	/* Ignore disabling init calib if more than losecs since instrument was open */
a1logd(p->log,3,"set_noinitcalib v = %d, ->lo_secs %d, losecs %d secs\n",v, m->lo_secs,losecs);
	if (v && losecs != 0 && m->lo_secs >= losecs) {
		a1logd(p->log,3,"initcalib disable ignored because %d >= %d secs\n",m->lo_secs,losecs);
		return;
	}
	m->noinitcalib = v;
}

/* Set the nocalibask mode */
/* Don't ask user for confirmation of calibration */
/* if the instrument is in the correct configuration for it. */
void munki_set_nocalibask(munki *p, int v) {
	munkiimp *m = (munkiimp *)p->m;

	m->nocalibask = v;
}

/* Set the trigger config */
void munki_set_trig(munki *p, inst_opt_type trig) {
	munkiimp *m = (munkiimp *)p->m;
	m->trig = trig;
}

/* Return the trigger config */
inst_opt_type munki_get_trig(munki *p) {
	munkiimp *m = (munkiimp *)p->m;
	return m->trig;
}

/* Switch thread handler */
static int munki_switch_thread(void *pp) {
	int nfailed = 0;
	munki *p = (munki *)pp;
	munkiimp *m = (munkiimp *)p->m;
	munki_code rv = MUNKI_OK; 
	a1logd(p->log,3,"Switch thread started\n");
//	for (nfailed = 0;nfailed < 5;)
	/* Try indefinitely, in case instrument is put to sleep */
	for (;;) {
		mk_eve ecode;

		rv = munki_waitfor_switch_th(p, &ecode, NULL, SW_THREAD_TIMEOUT);
		if (m->th_term) {
			m->th_termed = 1;
			break;
		}
		if (rv == MUNKI_INT_BUTTONTIMEOUT) {
			nfailed = 0;
			continue;
		}
		if (rv != MUNKI_OK) {
			nfailed++;
			a1logd(p->log,3,"Switch thread failed with 0x%x\n",rv);
			continue;
		}
		if (ecode == mk_eve_switch_press) {
			m->switch_count++;
			if (!m->hide_switch && p->eventcallback != NULL) {
				p->eventcallback(p->event_cntx, inst_event_switch);
			}
		} else if (ecode == mk_eve_spos_change) {
#ifdef FILTER_SPOS_EVENTS
			/* Signal change to filer thread */
			m->spos_msec = msec_time();
			m->spos_change++;
#else
			if (p->eventcallback != NULL) {
				p->eventcallback(p->event_cntx, inst_event_mconf);
			}
#endif
		}
	}
	a1logd(p->log,3,"Switch thread returning\n");
	return rv;
}

#ifdef FILTER_SPOS_EVENTS
static int munki_spos_thread(void *pp) {
	munki *p = (munki *)pp;
	munkiimp *m = (munkiimp *)p->m;
	int change = m->spos_change;		/* Current count */

	a1logd(p->log,3,"spos thread started\n");

	for (;;) {

		if (m->spos_th_term) {
			m->spos_th_termed = 1;
			break;
		}

		/* Do callback if change has persisted for 1 second */
		if (change != m->spos_change
		 && (msec_time() - m->spos_msec) >= FILTER_TIME) {
			change = m->spos_change;
			if (p->eventcallback != NULL) {
				p->eventcallback(p->event_cntx, inst_event_mconf);
			}
		}
		msec_sleep(100);
	}
	return 0;
}
#endif


/* ============================================================ */
/* Low level commands */

/* USB Instrument commands */

/* Read from the EEProm */
munki_code
munki_readEEProm(
	munki *p,
	unsigned char *buf,		/* Where to read it to */
	int addr,				/* Address in EEprom to read from */
	int size				/* Number of bytes to read (max 65535) */
) {
	munkiimp *m = (munkiimp *)p->m;
	int rwbytes;			/* Data bytes read or written */
	unsigned char pbuf[8];	/* Write EEprom parameters */
	int se, rv = MUNKI_OK;

	a1logd(p->log,2,"munki_readEEProm: address 0x%x size 0x%x\n",addr,size);

	if (size < 0 || addr < 0 || (addr + size) > (m->noeeblocks * m->eeblocksize))
		return MUNKI_INT_EEOUTOFRANGE;

	int2buf(&pbuf[0], addr);
	int2buf(&pbuf[4], size);
  	se = p->icom->usb_control(p->icom,
		               IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
	                   0x81, 0, 0, pbuf, 8, NULL, 2.0);

	if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
		a1logd(p->log,1,"munki_readEEProm: read failed (1) with ICOM err 0x%x\n",se);
		return rv;
	}

	/* Now read the bytes */
	se = p->icom->usb_read(p->icom, NULL, 0x81, buf, size, &rwbytes, 6.0);
	if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
		a1logd(p->log,1,"munki_readEEProm: read failed (2) with ICOM err 0x%x\n",se);
		return rv;
	}

	if (rwbytes != size) {
		a1logd(p->log,1,"munki_readEEProm: 0x%x bytes, short read error\n",rwbytes);
		return MUNKI_HW_EE_SHORTREAD;
	}

	if (p->log->debug >= 5) {
		int i;
		char oline[100] = { '\000' }, *bp = oline;
		for (i = 0; i < size; i++) {
			if ((i % 16) == 0)
				bp += sprintf(bp,"    %04x:",i);
			bp += sprintf(bp," %02x",buf[i]);
			if ((i+1) >= size || ((i+1) % 16) == 0) {
				bp += sprintf(bp,"\n");
				a1logd(p->log,5,oline);
				bp = oline;
			}
		}
	}

	a1logd(p->log,2,"munki_readEEProm: got 0x%x bytes, ICOM err 0x%x\n",rwbytes, se);

	return rv;
}



/* Get the firmware parameters */
/* return pointers may be NULL if not needed. */
munki_code
munki_getfirm(
	munki *p,
	int *fwrev,			/* Return the formware version number as 8.8 */
	int *tickdur,		/* Tick duration */
	int *minintcount,	/* Minimum integration tick count */
	int *noeeblocks,	/* Number of EEPROM blocks */
	int *eeblocksize	/* Size of each block */
) {
	unsigned char pbuf[24];	/* status bytes read */
	int _fwrev_maj, _fwrev_min;
	int _tickdur;
	int _minintcount;
	int _noeeblocks;
	int _eeblocksize;
	int se, rv = MUNKI_OK;

	a1logd(p->log,2,"munki_getfirm:\n");

	se = p->icom->usb_control(p->icom,
		               IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
	                   0x86, 0, 0, pbuf, 24, NULL, 2.0);

	if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
		a1logd(p->log,1,"munki_getfirm: failed with ICOM err 0x%x\n",se);
		return rv;
	}

	_fwrev_maj   = buf2int(&pbuf[0]);
	_fwrev_min   = buf2int(&pbuf[4]);
	_tickdur     = buf2int(&pbuf[8]);
	_minintcount = buf2int(&pbuf[12]);
	_noeeblocks  = buf2int(&pbuf[16]);
	_eeblocksize = buf2int(&pbuf[20]);

	a1logd(p->log,2,"munki_getfirm: returning fwrev %d.%d, tickdur %d, minint %d, eeblks %d, "
	       "eeblksz %d ICOM err 0x%x\n", _fwrev_maj, _fwrev_min, _tickdur, _minintcount,
	                                     _noeeblocks, _eeblocksize, se);

	if (fwrev != NULL) *fwrev = _fwrev_maj * 256 + _fwrev_min ;
	if (tickdur != NULL) *tickdur = _tickdur;
	if (minintcount != NULL) *minintcount = _minintcount;
	if (noeeblocks != NULL) *noeeblocks = _noeeblocks;
	if (eeblocksize != NULL) *eeblocksize = _eeblocksize;

	return rv;
}

/* Get the Chip ID */
munki_code
munki_getchipid(
	munki *p,
	unsigned char chipid[8]
) {
	int se, rv = MUNKI_OK;

	a1logd(p->log,2,"munki_getchipid: called\n");

	se = p->icom->usb_control(p->icom,
		               IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
	                   0x8A, 0, 0, chipid, 8, NULL, 2.0);

	if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
		a1logd(p->log,1,"munki_getchipid:  GetChipID failed with ICOM err 0x%x\n",se);
		return rv;
	}

	a1logd(p->log,2," GetChipID returns %02X-%02X%02X%02X%02X%02X%02X%02X ICOM err 0x%x\n",
                           chipid[0], chipid[1], chipid[2], chipid[3],
                           chipid[4], chipid[5], chipid[6], chipid[7], se);
	return rv;
}

/* Get the Version String */
munki_code
munki_getversionstring(
	munki *p,
	char vstring[100]
) {
	int se, rv = MUNKI_OK;
	int xferred = 0;

	a1logd(p->log,2,"munki_getversionstring: called\n");

	se = p->icom->usb_control(p->icom,
		               IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
	                   0x85, 0, 0, (unsigned char *)vstring, 100, &xferred, 2.0);

	if ((rv = icoms2munki_err(se)) != MUNKI_OK
	  && (se != ICOM_SHORT || xferred < 2)) { 
		
		a1logd(p->log,1,"munki_getversionstring: failed with ICOM err 0x%x xferred %d\n",se,xferred);
		return rv;
	}

	rv = MUNKI_OK;
	vstring[xferred-1] = '\000';		/* Make sure it's terminated... */

	a1logd(p->log,2,"munki_getversionstring: returning '%s' ICOM err 0x%x\n", vstring, se);

	return rv;
}

/* Get the measurement state */
/* return pointers may be NULL if not needed. */
munki_code
munki_getmeasstate(
	munki *p,
	int *ledtrange,		/* LED temperature range */
	int *ledtemp,		/* LED temperature */
	int *dutycycle,		/* Duty Cycle */
	int *ADfeedback		/* A/D converter feedback */
) {
	unsigned char pbuf[16];	/* values read */
	int _ledtrange;
	int _ledtemp;
	int _dutycycle;
	int _ADfeedback;
	int se, rv = MUNKI_OK;

	a1logd(p->log,2,"munki_getmeasstate: called\n");

	se = p->icom->usb_control(p->icom,
		               IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
	                   0x8F, 0, 0, pbuf, 16, NULL, 2.0);

	if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
		a1logd(p->log,1,"munki_getmeasstate: failed with ICOM err 0x%x\n",se);
		return rv;
	}

	_ledtrange   = buf2int(&pbuf[0]);
	_ledtemp     = buf2int(&pbuf[4]);
	_dutycycle   = buf2int(&pbuf[8]);
	_ADfeedback  = buf2int(&pbuf[12]);

	a1logd(p->log,2,"munki_getmeasstate: returning LED temp range %d, LED temp %d, "
	                "Duty Cycle %d, ADFeefback %d, ICOM err 0x%x\n",
	                _ledtrange, _ledtemp, _dutycycle, _ADfeedback, se);

	if (ledtrange != NULL) *ledtrange = _ledtrange;
	if (ledtemp != NULL) *ledtemp = _ledtemp;
	if (dutycycle != NULL) *dutycycle = _dutycycle;
	if (ADfeedback != NULL) *ADfeedback = _ADfeedback;

	return rv;
}

/* Get the device status */
/* return pointers may be NULL if not needed. */
munki_code
munki_getstatus(
	munki *p,
	mk_spos *spos,		/* Return the sensor position */
	mk_but *but			/* Return Button state */
) {
	unsigned char pbuf[2];	/* status bytes read */
	mk_spos _spos;
	mk_but _but;
	int se, rv = MUNKI_OK;

	a1logd(p->log,2,"munki_getstatus: called\n");

	se = p->icom->usb_control(p->icom,
		               IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
	                   0x87, 0, 0, pbuf, 2, NULL, 2.0);

	if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
		a1logd(p->log,1,"munki_getstatus: failed with ICOM err 0x%x\n",se);
		return rv;
	}

	_spos = (mk_spos)pbuf[0];
	_but   = (mk_but)pbuf[1];

	if (p->log->debug >= 3) {
		char sb1[50], sb2[50];
		if (_spos == mk_spos_proj)
			strcpy(sb1, "Projector");
		else if (_spos == mk_spos_surf)
			strcpy(sb1, "Surface");
		else if (_spos == mk_spos_calib)
			strcpy(sb1, "Calibration");
		else if (_spos == mk_spos_amb)
			strcpy(sb1, "Ambient");
		else
			sprintf(sb1,"Unknown 0x%x",_spos);
		if (_but == mk_but_switch_release)
			strcpy(sb2, "Released");
		else if (_but == mk_but_switch_press)
			strcpy(sb2, "Pressed");
		else
			sprintf(sb2,"Unknown 0x%x",_but);

		a1logd(p->log,3,"munki_getstatus: Sensor pos. %s, Button state %s, ICOM err 0x%x\n",
	            sb1, sb2, se);
	}

	if (spos != NULL) *spos = _spos;
	if (but != NULL) *but = _but;

	return rv;
}

/* Set the indicator LED state (advanced) */
/* NOTE that the instrument seems to turn it off */
/* whenever any other sort of operation occurs. */
munki_code
munki_setindled(
    munki *p,
    int p1,			/* On time (msec) */
    int p2,			/* Off time (msec) */
    int p3,			/* Transition time (msec) */
    int p4,			/* Number of pulses, -1 = max */
    int p5			/* Ignored ? */
) {
	unsigned char pbuf[20];	/* command bytes written */
	int se, rv = MUNKI_OK;

	a1logd(p->log,2,"munki_setindled: %d, %d, %d, %d, %d\n",
	                   p1, p2, p3, p4, p5);

	int2buf(&pbuf[0], p1);		/* On time (msec) */
	int2buf(&pbuf[4], p2);		/* Off time (msec) */
	int2buf(&pbuf[8], p3);		/* Transition time (msec) */
	int2buf(&pbuf[12], p4);		/* Number of pulses, -1 = max */
	int2buf(&pbuf[16], p5);		/* Unknown */

	se = p->icom->usb_control(p->icom,
		               IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
	                   0x92, 0, 0, pbuf, 20, NULL, 2.0);

	if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
		a1logd(p->log,1,"munki_setindled: failed with ICOM err 0x%x\n",se);
		return rv;
	}

	a1logd(p->log,2,"munki_setindled: OK ICOM err 0x%x\n",se);

	return rv;
}

/* Trigger a measurement with the given measurement parameters */
munki_code
munki_triggermeasure(
	munki *p,
	int intclocks,		/* Number of integration clocks */
	int nummeas,		/* Number of measurements to make */
	int measmodeflags,	/* Measurement mode flags */
	int holdtempduty	/* Hold temperature duty cycle */
) {
	munkiimp *m = (munkiimp *)p->m;
	unsigned char pbuf[12];	/* command bytes written */
	int se, rv = MUNKI_OK;

	a1logd(p->log,2,"munki_triggermeasure: lamp %d, scan %d, gain %d, intclks %d, nummeas %d\n",
	          (measmodeflags & MUNKI_MMF_LAMP) ? 1 : 0,
	          (measmodeflags & MUNKI_MMF_SCAN) ? 1 : 0,
	          (measmodeflags & MUNKI_MMF_HIGHGAIN) ? 1 : 0,
              intclocks, nummeas);

	pbuf[0] = (measmodeflags & MUNKI_MMF_LAMP) ? 1 : 0;
	pbuf[1] = (measmodeflags & MUNKI_MMF_SCAN) ? 1 : 0;
	pbuf[2] = (measmodeflags & MUNKI_MMF_HIGHGAIN) ? 1 : 0;
	pbuf[3] = holdtempduty;
	int2buf(&pbuf[4], intclocks);
	int2buf(&pbuf[8], nummeas);

	m->tr_t1 = m->tr_t2 = m->tr_t3 = m->tr_t4 = m->tr_t5 = m->tr_t6 = m->tr_t7 = 0;
	m->tr_t1 = msec_time();     /* Diagnostic */

	se = p->icom->usb_control(p->icom,
		               IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
	                   0x80, 0, 0, pbuf, 12, NULL, 2.0);
	m->trigstamp = usec_time();

	m->tr_t2 = msec_time();     /* Diagnostic */

	if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
		a1logd(p->log,1,"munki_triggermeasure: failed with ICOM err 0x%x\n",se);
		return rv;
	}

	a1logd(p->log,2,"munki_triggermeasure: OK ICOM err 0x%x\n",se);

	return rv;
}

/* Read a measurements results. */
/* A buffer full of bytes is returned. */
static munki_code
munki_readmeasurement(
	munki *p,
	int inummeas,			/* Initial number of measurements to expect */
	int scanflag,			/* NZ if in scan mode to continue reading */
	unsigned char *buf,		/* Where to read it to */
	int bsize,				/* Bytes available in buffer */
	int *nummeas,			/* Return number of readings measured */
	int calib_measure,		/* flag - nz if this is a calibration measurement */
	int dark_measure		/* flag - nz if this is a dark measurement */
) {
	munkiimp *m = (munkiimp *)p->m;
	unsigned char *ibuf = buf;	/* Incoming buffer */
	int nmeas;					/* Number of measurements for this read */
	double top, extra;			/* Time out period */
	int rwbytes;				/* Data bytes read or written */
	int se, rv = MUNKI_OK;
	int treadings = 0;
//	int gotshort = 0;			/* nz when got a previous short reading */

	if ((bsize % (m->nsen * 2)) != 0) {
		a1logd(p->log,1,"munki_readmeasurement: got %d bytes, nsen = %d\n",bsize,m->nsen);
		return MUNKI_INT_ODDREADBUF;
	}

	extra = 1.0;		/* Extra timeout margin */

#ifdef SINGLE_READ
	if (scanflag == 0)
		nmeas = inummeas;
	else
		nmeas = bsize / (m->nsen * 2);		/* Use a single large read */
#else
	nmeas = inummeas;				/* Smaller initial number of measurements */
#endif

	top = extra + m->c_inttime * nmeas;

	a1logd(p->log,2,"munki_readmeasurement: inummeas %d, scanflag %d, address %p bsize 0x%x, timeout %f\n",inummeas, scanflag, buf, bsize, top);

	for (;;) {
		int size;		/* number of bytes to read */

		size = (m->nsen * 2) * nmeas;

		if (size > bsize) {		/* oops, no room for read */
			a1logd(p->log,1,"munki_readmeasurement: Buffer was too short for scan\n");
			return MUNKI_INT_MEASBUFFTOOSMALL;
		}

		m->tr_t6 = msec_time();	/* Diagnostic, start of subsequent reads */
		if (m->tr_t3 == 0) m->tr_t3 = m->tr_t6;		/* Diagnostic, start of first read */

		a1logd(p->log,5,"about to call usb_read with %d bytes\n",size);
		se = p->icom->usb_read(p->icom, NULL, 0x81, buf, size, &rwbytes, top);

		m->tr_t5 = m->tr_t7;
		m->tr_t7 = msec_time();	/* Diagnostic, end of subsequent reads */
		if (m->tr_t4 == 0) {
			m->tr_t5 = m->tr_t2;
			m->tr_t4 = m->tr_t7;	/* Diagnostic, end of first read */
		}

#ifdef NEVER		/* Use short + timeout to terminate scan */
		if (gotshort != 0 && se == ICOM_TO) {	/* We got a timeout after a short read. */ 
			a1logd(p->log,1,"Read timed out in %f secs after getting short read\n"
			                "(Trig & rd times %d %d %d %d)\n",
			         top,
			         m->tr_t2-m->tr_t1, m->tr_t3-m->tr_t2, m->tr_t4-m->tr_t3, m->tr_t6-m->tr_t5);
			break;		/* We're done */
		} else
#endif
		if (se == ICOM_SHORT) {		/* Expect this to terminate scan reading */
			a1logd(p->log,5,"Short read, read %d bytes, asked for %d\n"
			                "(Trig & rd times %d %d %d %d)\n",
					 rwbytes,size,
				     m->tr_t2-m->tr_t1, m->tr_t3-m->tr_t2, m->tr_t4-m->tr_t3, m->tr_t6-m->tr_t5);
		} else if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
			if (m->trig_rv != MUNKI_OK) {
				a1logd(p->log,1,"munki_readmeasurement: trigger failed, ICOM err 0x%x\n",m->trig_se);
				return m->trig_rv;
			}
			if (se & ICOM_TO)
				a1logd(p->log,1,"munki_readmeasurement: read timed out with top = %f\n",top);
		
			a1logd(p->log,1,"munki_readmeasurement: read failed, bytes read 0x%x, ICOM err 0x%x\n",rwbytes, se);
			return rv;
		}
 
		/* If we didn't read a multiple of m->nsen * 2, we've got problems */
		if ((rwbytes % (m->nsen * 2)) != 0) {
			a1logd(p->log,1,"munki_readmeasurement: read %d bytes, nsen %d, odd read error\n",rwbytes, m->nsen);
			return MUNKI_HW_ME_ODDREAD;
		}

		/* Track where we're up to */
		bsize -= rwbytes;
		buf   += rwbytes;
		treadings += rwbytes/(m->nsen * 2);

		if (scanflag == 0) {	/* Not scanning */

			/* Expect to read exactly what we asked for */
			if (rwbytes != size) {
				a1logd(p->log,1,"munki_readmeasurement: unexpected short read, got %d expected %d\n",rwbytes,size);
				return MUNKI_HW_ME_SHORTREAD;
			}
			break;	/* And we're done */
		}

#ifdef NEVER		/* Use short + timeout to terminate scan */
		/* We expect to get a short read at the end of a scan, */
		/* or we might have the USB transfer truncated by somethinge else. */
		/* Note the short read, and keep reading until we get a time out */
		if (rwbytes != size) {
			gotshort = 1;
		} else {
			gotshort = 0;
		}
#else				/* Use short to terminate scan */
		/* We're scanning and expect to get a short read at the end of the scan. */
		if (rwbytes != size) {
			a1logd(p->log,5,"done because read %d bytes != %d\n",rwbytes,size);
			break;
		}
#endif

		if (bsize == 0) {		/* oops, no room for more scanning read */
			unsigned char tbuf[NSEN_MAX * 2];

			/* We need to clean up, so soak up all the data and throw it away */
			while ((se = p->icom->usb_read(p->icom, NULL, 0x81, tbuf, m->nsen * 2, &rwbytes, top)) == ICOM_OK)
				;
			a1logd(p->log,1,"munki_readmeasurement: buffer was too short for scan\n");
			return MUNKI_INT_MEASBUFFTOOSMALL;
		}

		/* Read a bunch more readings until the read is short or times out */
		nmeas = bsize / (m->nsen * 2);
		if (nmeas > 64)
			nmeas = 64;
		top = extra + m->c_inttime * nmeas;
	}

	/* Must have timed out in initial readings */
	if (treadings < inummeas) {
		a1logd(p->log,1,"munki_readmeasurement: read failed, bytes read 0x%x, ICOM err 0x%x\n",rwbytes, se);
		return MUNKI_RD_SHORTMEAS;
	} 

	if (p->log->debug >= 5) {
		int i, size = treadings * m->nsen * 2;
		char oline[100] = { '\000' }, *bp = oline;
		for (i = 0; i < size; i++) {
			if ((i % 16) == 0)
				bp += sprintf(bp,"    %04x:",i);
			bp += sprintf(bp," %02x",ibuf[i]);
			if ((i+1) >= size || ((i+1) % 16) == 0) {
				bp += sprintf(bp,"\n");
				a1logd(p->log,5,oline);
				bp = oline;
			}
		}
	}

	a1logd(p->log,2,"munki_readmeasurement: Read %d readings, ICOM err 0x%x\n"
	                "(Trig & rd times %d %d %d %d)\n",
	                 treadings, se,
					 m->tr_t2-m->tr_t1, m->tr_t3-m->tr_t2, m->tr_t4-m->tr_t3, m->tr_t6-m->tr_t5);

	if (nummeas != NULL) *nummeas = treadings;

	return rv;
}

/* Simulating an event */
munki_code munki_simulate_event(munki *p, mk_eve ecode,  int timestamp) {
	munkiimp *m = (munkiimp *)p->m;
	unsigned char pbuf[8];	/* 8 bytes to write */
	int se, rv = MUNKI_OK;

	a1logd(p->log,2,"munki_simulate_event: 0x%x\n",ecode);

	int2buf(&pbuf[0], ecode);
	int2buf(&pbuf[4], timestamp);	/* msec since munki power up */

	se = p->icom->usb_control(p->icom,
		               IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
	                   0x8E, 0, 0, pbuf, 8, NULL, 2.0);

	if ((rv = icoms2munki_err(se)) != MUNKI_OK)
		a1logd(p->log,1,"munki_simulate_event: event 0x%x failed with ICOM err 0x%x\n",ecode,se);
	else 
		a1logd(p->log,2,"munki_simulate_event: 0x%x done, ICOM err 0x%x\n",ecode,se);

	/* Cancel the I/O in case there is no response*/
	msec_sleep(50);
	if (m->th_termed == 0) {
		a1logd(p->log,1,"munki_simulate_event: terminate switch thread failed, canceling I/O\n");
		p->icom->usb_cancel_io(p->icom, &m->sw_cancel);
	}

	return rv;
}

/* Wait for a reply triggered by an event */
munki_code munki_waitfor_switch(munki *p, mk_eve *ecode, int *timest, double top) {
	int rwbytes;			/* Data bytes read */
	unsigned char buf[8];	/* Result  */
	int se, rv = MUNKI_OK;
	mk_eve _ecode;
	int _timest;

	a1logd(p->log,2,"munki_waitfor_switch: Read 8 bytes from switch hit port\n");

	/* Now read 8 bytes */
	se = p->icom->usb_read(p->icom, NULL, 0x83, buf, 8, &rwbytes, top);

	if (se & ICOM_TO) {
		a1logd(p->log,1,"munki_waitfor_switch: read 0x%x bytes, timed out\n",rwbytes);
		return MUNKI_INT_BUTTONTIMEOUT;
	}

	if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
		a1logd(p->log,2,"munki_waitfor_switch: read failed with ICOM err 0x%x\n",se);
		return rv;
	}

	if (rwbytes != 8) {
		a1logd(p->log,1,"munki_waitfor_switch: read %d bytes, short read error\n",rwbytes);
		return MUNKI_HW_EE_SHORTREAD;
	}

	_ecode  = (mk_eve) buf2int(&buf[0]);
	_timest = buf2int(&buf[4]);
	
	if (p->log->debug >= 3) {
		char sbuf[100];
		if (_ecode == mk_eve_none)
			strcpy(sbuf, "None");
		else if (_ecode == mk_eve_switch_press)
			strcpy(sbuf, "Button press");
		else if (_ecode == mk_eve_switch_release)
			strcpy(sbuf, "Button release");
		else if (_ecode == mk_eve_spos_change)
			strcpy(sbuf, "Sensor position change");
		else
			sprintf(sbuf,"Unknown 0x%x",_ecode);

		a1logd(p->log,3,"munki_waitfor_switch: Event %s, timestamp %d ICOM err 0x%x\n", sbuf, _timest, se);
	}

	a1logd(p->log,2,"munki_waitfor_switch: read %d bytes OK\n",rwbytes);

	if (ecode != NULL) *ecode = _ecode;
	if (timest != NULL) *timest = _timest;

	return rv; 
}

/* Wait for a reply triggered by a key press or config change (thread version) */
/* Returns MUNKI_OK if the switch has been pressed, */
/* or MUNKI_INT_BUTTONTIMEOUT if */
/* no switch was pressed befor the time expired, */
/* or some other error. */
munki_code munki_waitfor_switch_th(munki *p, mk_eve *ecode, int *timest, double top) {
	munkiimp *m = (munkiimp *)p->m;
	int rwbytes;			/* Data bytes read */
	unsigned char buf[8];	/* Result  */
	int se, rv = MUNKI_OK;
	mk_eve _ecode;
	int _timest;

	a1logd(p->log,2,"munki_waitfor_switch_th: Read 8 bytes from switch hit port\n");

	/* Now read 8 bytes */
	se = p->icom->usb_read(p->icom, &m->sw_cancel, 0x83, buf, 8, &rwbytes, top);

	if (se & ICOM_TO) {
		a1logd(p->log,1,"munki_waitfor_switch_th: read 0x%x bytes, timed out\n",rwbytes);
		return MUNKI_INT_BUTTONTIMEOUT;
	}

	if ((rv = icoms2munki_err(se)) != MUNKI_OK) {
		a1logd(p->log,2,"munki_waitfor_switch_th: read failed with ICOM err 0x%x\n",se);
		return rv;
	}

	if (rwbytes != 8) {
		a1logd(p->log,1,"munki_waitfor_switch_th: read %d bytes, short read error\n",rwbytes);
		return MUNKI_HW_EE_SHORTREAD;
	}

	_ecode  = (mk_eve) buf2int(&buf[0]);
	_timest = buf2int(&buf[4]);			/* msec since munki power up */

	if (p->log->debug >= 3) {
		char sbuf[100];
		if (_ecode == mk_eve_none)
			strcpy(sbuf, "None");
		else if (_ecode == mk_eve_switch_press)
			strcpy(sbuf, "Button press");
		else if (_ecode == mk_eve_switch_release)
			strcpy(sbuf, "Button release");
		else if (_ecode == mk_eve_spos_change)
			strcpy(sbuf, "Sensor position change");
		else
			sprintf(sbuf,"Unknown 0x%x",_ecode);

		a1logd(p->log,3,"munki_waitfor_switch_th: Event %s, timestamp %d ICOM err 0x%x\n", sbuf, _timest, se);
	}

	a1logd(p->log,2,"munki_waitfor_switch_th: read %d bytes OK\n",rwbytes);

	if (ecode != NULL) *ecode = _ecode;
	if (timest != NULL) *timest = _timest;

	return rv; 
}


/* ============================================================ */
/* key/value dictionary support for EEProm contents */

/* Fixup values for the window reference */

/* Check values */
static double proj_check[36] = {
	0.0,
	0.0,
	0.0,
	0.0,
	0.0,
	0.0,
	0.827859997749328610,
	0.849550008773803710,
	0.855490028858184810,
	0.858709990978240970,
	0.861010015010833740,
	0.862879991531372070,
	0.864109992980957030,
	0.864619970321655270,
	0.865379989147186280,
	0.865629971027374270,
	0.865689992904663090,
	0.865499973297119140,
	0.865499973297119140,
	0.865760028362274170,
	0.866209983825683590,
	0.866630017757415770,
	0.867579996585845950,
	0.868969976902008060,
	0.870270013809204100,
	0.871270000934600830,
	0.872340023517608640,
	0.873269975185394290,
	0.873669981956481930,
	0.873640000820159910,
	0.874390006065368650,
	0.873179972171783450,
	0.872720003128051760,
	0.872640013694763180,
	0.873160004615783690,
	0.873440027236938480
};


/* Correction values to emission ref. */
static double proj_fix[36] = {
	0.639684915542602540,
	0.639684915542602540,
	0.639684915542602540,
	0.812916100025177000,
	0.846581041812896730,
	0.854855418205261230,
	0.859299719333648680,
	0.861804306507110600,
	0.863713920116424560,
	0.865424513816833500,
	0.866307735443115230,
	0.867028772830963130,
	0.867631316184997560,
	0.868214190006256100,
	0.868206322193145750,
	0.868299305438995360,
	0.867988884449005130,
	0.868103504180908200,
	0.868657410144805910,
	0.869595944881439210,
	0.870542407035827640,
	0.871895790100097660,
	0.873195052146911620,
	0.874702811241149900,
	0.876054167747497560,
	0.877129673957824710,
	0.877931654453277590,
	0.877546310424804690,
	0.876341819763183590,
	0.875181615352630620,
	0.875020027160644530,
	0.875684559345245360,
	0.876559674739837650,
	0.876724362373352050,
	0.876553714275360110,
	0.875786423683166500
};

/* Initialise the calibration from the EEProm contents. */
/* (We're handed a buffer that's been rounded up to an even 32 bits by */
/* padding with zero's) */
munki_code munki_parse_eeprom(munki *p, unsigned char *buf, unsigned int len) {
	munkiimp *m = (munkiimp *)p->m;
	mkdata *d;
	int rv = MUNKI_OK;
	unsigned int chsum, sum;
	int calver, compver;			/* Calibration version and compatiblity version */
	unsigned char chipid[8];		/* Calibration chip id */
	int tint, *tinta;				/* Temporary */
	double tdouble;					/* Temporary */
	int i, j;

	a1logd(p->log,2,"munki_parse_eeprom: called with %d bytes\n",len);

	/* Check the checksum */
	chsum = buf2uint(buf+8);
	int2buf(buf+8, 0);				/* Zero it out */
	
	for (sum = 0, i = 0; i < (len-3); i += 4) {
		sum += buf2uint(buf + i);
	}



	a1logd(p->log,3,"munki_parse_eeprom: cal chsum = 0x%x, should be 0x%x - %s\n",sum,chsum, sum == chsum ? "OK": "BAD");
	if (sum != chsum)
		return MUNKI_INT_CALBADCHSUM;


	/* Create class to handle EEProm parsing */
	if ((d = new_mkdata(p, buf, len)) == NULL)
		return MUNKI_INT_CREATE_EEPROM_STORE;

	/* Check out the version */
	if (d->get_u16_ints(d, &calver, 0, 1) == NULL)
		return MUNKI_DATA_RANGE;
	if (d->get_u16_ints(d, &compver, 2, 1) == NULL)
		return MUNKI_DATA_RANGE;
	a1logd(p->log,4,"cal version = %d, compatible with %d\n",calver,compver);

	/* We understand versions 3 to 6 */

	if (calver < 3 || compver < 3 || compver > 6)
		return MUNKI_HW_CALIBVERSION;

	/* Choose the version we will treat it as */
	if (calver > 6 && compver <= 6)
		m->calver = 6;
	else
		m->calver = calver;
	a1logd(p->log,4,"Treating as cal version = %d\n",m->calver);

	/* Parse all the calibration info common for vers 3 - 6 */

	/* Production number */
	if (d->get_32_ints(d, &m->prodno, 12, 1) == NULL)
		return MUNKI_DATA_RANGE;
	a1logd(p->log,4,"Produnction no = %d\n",m->prodno);

	/* Chip HW ID */
	if (d->get_8_char(d, (unsigned char *)chipid, 16, 8) == NULL)
		return MUNKI_DATA_RANGE;
	a1logd(p->log,4,"HW Id = %02x-%02x%02x%02x%02x%02x%02x%02x\n",
		                           chipid[0], chipid[1], chipid[2], chipid[3],
		                           chipid[4], chipid[5], chipid[6], chipid[7]);

#ifndef FAKE_EEPROM				/* Get [und] EEPROM data from munki_fake_eeprom.h */
	/* Check that the chipid matches the calibration */
	for (i = 0; i < 8; i++) {
		if (chipid[i] != m->chipid[i]) {
			for (i = 0; i < 8; i++) {
				if (m->chipid[i] != 0xff)
					break;
			}
			if (i < 8)
				return MUNKI_HW_CALIBMATCH;
			return MUNKI_HW_NOSENSOR;
		}
	}
#endif

	/* Serial number */
	if (d->get_8_asciiz(d, m->serno, 24, 16) == NULL)
		return MUNKI_DATA_RANGE;
	a1logd(p->log,4,"serial number '%s'\n",m->serno);

	/* Underlying calibration information */

	m->nsen = 137;			/* Sensor bands stored */
	m->nraw = 128;			/* Raw bands stored */
	m->nwav1 = 36;			/* Standard res number of cooked spectrum band */
	m->wl_short1 = 380.0;	/* Standard res short and long wavelengths */
	m->wl_long1 = 730.0;

	/* Fill this in here too */
	m->wl_short2 = HIGHRES_SHORT;
	m->wl_long2 = HIGHRES_LONG;
	m->nwav2 = (int)((m->wl_long2-m->wl_short2)/HIGHRES_WIDTH + 0.5) + 1;	

	/* Reflection wavelength calibration information */
	/* This is setup assuming 128 raw bands, starting */
	/* at offset 6 from the values returned by the hardware. */
	if ((m->rmtx_index1 = d->get_32_ints(d, NULL, 40, 36)) == NULL)
		return MUNKI_DATA_RANGE;

	/* Fake the number of matrix cooeficients for each out wavelength */
	if ((m->rmtx_nocoef1 = (int *)malloc(sizeof(int) * 36)) == NULL)
		return MUNKI_DATA_MEMORY;
	for (i = 0; i < 36; i++)
		m->rmtx_nocoef1[i] = 16;

	if ((m->rmtx_coef1 = d->get_32_doubles(d, NULL, 184, 36 * 16)) == NULL)
		return MUNKI_DATA_RANGE;

	if (p->log->debug >= 7) {
		a1logd(p->log,7,"Reflectance matrix:\n");
		for(i = 0; i < 36; i++) {
			a1logd(p->log,7," Wave %d, index %d\n",i, m->rmtx_index1[i]);
			for (j = 0; j < 16; j++) {
				if (m->rmtx_coef1[i * 16 + j] != 0.0)
					a1logd(p->log,7,"  Wt %d =  %f\n",j, m->rmtx_coef1[i * 16 + j]);
			}
		}
	}

	/* Emission wavelength calibration information */
	if ((m->emtx_index1 = d->get_32_ints(d, NULL, 2488, 36)) == NULL)
		return MUNKI_DATA_RANGE;

	/* Fake the number of matrix cooeficients for each out wavelength */
	if ((m->emtx_nocoef1 = (int *)malloc(sizeof(int) * 36)) == NULL)
		return MUNKI_DATA_MEMORY;
	for (i = 0; i < 36; i++)
		m->emtx_nocoef1[i] = 16;

	if ((m->emtx_coef1 = d->get_32_doubles(d, NULL, 2632, 36 * 16)) == NULL)
		return MUNKI_DATA_RANGE;

	if (p->log->debug >= 7) {
		a1logd(p->log,5,"Emmission matrix:\n");
		for(i = 0; i < 36; i++) {
			a1logd(p->log,7," Wave %d, index %d\n",i, m->emtx_index1[i]);
			for (j = 0; j < 16; j++) {
				if (m->emtx_coef1[i * 16 + j] != 0.0)
					a1logd(p->log,7,"  Wt %d =  %f\n",j, m->emtx_coef1[i * 16 + j]);
			}
		}
	}

	/* Linearization */
	if ((m->lin0 = d->rget_32_doubles(d, NULL, 4936, 4)) == NULL)
		return MUNKI_DATA_RANGE;
	m->nlin0 = 4;

	if ((m->lin1 = d->rget_32_doubles(d, NULL, 4952, 4)) == NULL)
		return MUNKI_DATA_RANGE;
	m->nlin1 = 4;

	if (p->log->debug >= 3) {
		char oline[200] = { '\000' }, *bp = oline;

		bp += sprintf(bp,"Normal non-lin    =");
		for(i = 0; i < m->nlin0; i++)
			bp += sprintf(bp," %1.10f",m->lin0[i]);
		bp += sprintf(bp,"\n");
		a1logd(p->log,2,oline);

		bp = oline;
		bp += sprintf(bp,"High Gain non-lin =");
		for(i = 0; i < m->nlin1; i++)
			bp += sprintf(bp," %1.10f",m->lin1[i]);
		bp += sprintf(bp,"\n");
		a1logd(p->log,2,oline);
	}

	/* Reflectance reference */
	if ((m->white_ref1 = d->get_32_doubles(d, NULL, 4968, 36)) == NULL)
		return MUNKI_DATA_RANGE;

	/* Emission reference */
	if ((m->emis_coef1 = d->get_32_doubles(d, NULL, 5112, 36)) == NULL)
		return MUNKI_DATA_RANGE;

	/* Ambient reference */
	if ((m->amb_coef1 = d->get_32_doubles(d, NULL, 5256, 36)) == NULL)
		return MUNKI_DATA_RANGE;

	/* Sensor target values */
	if (d->get_u16_ints(d, &tint, 5400, 1) == NULL)
		return MUNKI_DATA_RANGE;
	m->minsval = (double)tint;
	if (d->get_u16_ints(d, &tint, 5402, 1) == NULL)
		return MUNKI_DATA_RANGE;
	m->optsval = (double)tint;
	if (d->get_u16_ints(d, &tint, 5404, 1) == NULL)
		return MUNKI_DATA_RANGE;
	m->maxsval = (double)tint;
	if (d->get_u16_ints(d, &tint, 5406, 1) == NULL)
		return MUNKI_DATA_RANGE;
	m->satlimit = (double)tint;

	a1logd(p->log,4,"Sensor targmin %.0f, opt %.0f, max %.0f, sat %.0f\n",
	                           m->minsval,m->optsval,m->maxsval,m->satlimit);

	if (d->get_32_doubles(d, &m->cal_int_time, 5408, 1) == NULL)
		return MUNKI_DATA_RANGE;
	m->cal_int_time *= 1e-3;				/* Convert to seconds */

	if (d->get_32_ints(d, &tint, 5412, 1) == NULL)
		return MUNKI_DATA_RANGE;
	m->ledpreheattime = tint * 1e-3;	/* Convert to seconds */

	if (d->get_32_ints(d, &tint, 5416, 1) == NULL)
		return MUNKI_DATA_RANGE;
	m->ledwaittime = tint * 1e-3;		/* Convert to seconds */

	if (d->get_u16_ints(d, &m->ledholdtempdc, 5420, 1) == NULL)
		return MUNKI_DATA_RANGE;

	a1logd(p->log,4,"Cal int time %f, LED pre-heat %f, Led wait %f, LED hold temp duty cycle %d\n", m->cal_int_time, m->ledpreheattime, m->ledwaittime, m->ledholdtempdc);

	if (d->get_u16_ints(d, &tint, 5422, 1) == NULL)
		return MUNKI_DATA_RANGE;
	m->refinvalidsampt = tint * 1e-3;	/* Convert to seconds */

	if (d->get_32_ints(d, &tint, 5424, 1) == NULL)
		return MUNKI_DATA_RANGE;
	m->calscantime = tint * 1e-3;	/* Convert to seconds */

	a1logd(p->log,4,"Invalid sample time %f, Cal scan time %f\n",
	                           m->refinvalidsampt, m->calscantime);

	/* Stray light compensation. Note that 16 bit numbers are signed. */
	if ((tinta = d->get_16_ints(d, NULL, 5428, 36 * 36)) == NULL)
		return MUNKI_DATA_RANGE;
	if (m->calver >= 4) {
		if (d->get_32_doubles(d, &tdouble, 8020, 1) == NULL)
			return MUNKI_DATA_RANGE;
	} else {
		tdouble = 0.001;		/* Hmm. this is quite different to EEProm value */
	}
	/* Convert from ints to floats */
	m->straylight1 = dmatrixz(0, 35, 0, 35);  
	for (i = 0; i < 36; i++) {
		for (j = 0; j < 36; j++) {
			m->straylight1[i][j] = tdouble * tinta[i * 36 + j];
			if (i == j)
				m->straylight1[i][j] += 1.0;
		}
	}
	free(tinta);

	if (p->log->debug >= 7) {
		a1logd(p->log,7,"Stray Light matrix:\n");
		for(i = 0; i < 36; i++) {
			double sum = 0.0;
			a1logd(p->log,7," Wave %d, index %d\n",i, m->rmtx_index1[i]);
			for (j = 0; j < 36; j++) {
				sum += m->straylight1[i][j];
				a1logd(p->log,7,"  Wt %d = %f\n",j, m->straylight1[i][j]);
			}
			a1logd(p->log,7,"  Sum = %f\n",sum);
		}
	}

	if (m->calver >= 5) {
		/* Projector reference */
		if ((m->proj_coef1 = d->get_32_doubles(d, NULL, 8024, 36)) == NULL)
			return MUNKI_DATA_RANGE;

		/* Apparently this can be faulty though. Check if it is */
		for (i = 0; i < 6; i++) {
			if (m->proj_coef1[i] != m->proj_coef1[i])
				break;					/* Not Nan */
		}
		if (i == 6) {				/* First 6 are Nan's */
			for (; i < 36; i++) {
				if ((m->emis_coef1[i]/m->proj_coef1[i] - proj_check[i]) > 0.001)
					break;			/* Not less than 0.001 */
			}
		}
		if (i == 36) {		/* It's faulty */
			free(m->proj_coef1);
			m->proj_coef1 = NULL;		/* Fall through to fakeup */
		}
	}

	if (m->proj_coef1 == NULL) {		/* Fake up a projector reference */
		if ((m->proj_coef1 = (double *)malloc(sizeof(double) * 36)) == NULL)
			return MUNKI_DATA_MEMORY;
		for (i = 0; i < 36; i++) {
			m->proj_coef1[i] = m->emis_coef1[i]/proj_fix[i];
		}
		a1logd(p->log,4,"Faked up projector cal reference\n");
	}

	if (m->calver >= 6) {
		if (d->get_8_ints(d, &m->adctype, 8168, 1) == NULL)
			return MUNKI_DATA_RANGE;
	} else {
		m->adctype = 0;
	}

	if (p->log->debug >= 7) {
		a1logd(p->log,4,"White ref, emission cal, ambient cal, proj cal:\n");
		for(i = 0; i < 36; i++) {
			a1logd(p->log,7," %d: %f, %f, %f, %f\n",i, m->white_ref1[i], m->emis_coef1[i],
			                                          m->amb_coef1[i], m->proj_coef1[i]);
		}
	}

#ifdef PLOT_RCALCURVE
	/* Plot the reflection reference curve */
	{
		int i;
		double xx[36];
		double y1[36];
	
		for (i = 0; i < m->nwav1; i++) {
			xx[i] = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, i);
			y1[i] = m->white_ref1[i];
		}
		plot_msg("Reflection Reference (Black)\n");
		do_plot(xx, y1, NULL, NULL, 36);
	}
#endif /* PLOT_RCALCURVE */

#ifdef PLOT_ECALCURVES
	/* Plot the emission reference curves */
	{
		int i;
		double xx[36];
		double y1[36], y2[36], y3[36];
	
		plot_msg("Emission Reference (Black), Ambient (Red), Projector (Green)\n");
		for (i = 0; i < m->nwav1; i++) {
			xx[i] = XSPECT_WL(m->wl_short1, m->wl_long1, m->nwav1, i);
			y1[i] = m->emis_coef1[i];
			y2[i] = m->amb_coef1[i];
			y3[i] = m->proj_coef1[i];
			if (y3[i] > 0.02)
				y3[i] = 0.02;
		}
		do_plot(xx, y1, y2, y3, 36);
	}
#endif /* PLOT_ECALCURVES */

	/* Default to standard resolution */
	m->nwav = m->nwav1;
	m->wl_short = m->wl_short1;
	m->wl_long = m->wl_long1; 

	m->rmtx_index  = m->rmtx_index1;
	m->rmtx_nocoef = m->rmtx_nocoef1;
	m->rmtx_coef   = m->rmtx_coef1;
	m->emtx_index  = m->emtx_index1;
	m->emtx_nocoef = m->emtx_nocoef1;
	m->emtx_coef   = m->emtx_coef1;

	m->white_ref = m->white_ref1;
	m->emis_coef = m->emis_coef1;
	m->amb_coef  = m->amb_coef1;
	m->proj_coef = m->proj_coef1;
	m->straylight = m->straylight1;
	
	m->highgain = 1.0/m->lin1[1];	/* Gain is encoded in linearity */
	a1logd(p->log,3, "highgain = %f\n",m->highgain);

	d->del(d);

	return rv;
}

		
/* Return a pointer to an array of chars containing data from 8 bits. */
/* If rv is NULL, the returned value will have been allocated, othewise */
/* the rv will be returned. Return NULL if out of range. */
static unsigned char *mkdata_get_8_char(struct _mkdata *d, unsigned char *rv, int off, int count) {
	int i;

	if (count <= 0
	 || off < 0
	 || (off + count * 1) > d->len)
		return NULL;

	if (rv == NULL) {
		if ((rv = (unsigned char *)malloc(sizeof(int) * count)) == NULL)
			return NULL;
	}

	for (i = 0; i < count; i++, off += 1) {
		rv[i] = d->buf[off];
	}
	return rv;
}

/* Return a pointer to an nul terminated string containing data from 8 bits. */
/* If rv is NULL, the returned value will have been allocated, othewise */
/* the rv will be returned. Return NULL if out of range. */
/* An extra space and a nul terminator will be added to the eeprom data */
static char *mkdata_get_8_asciiz(struct _mkdata *d, char *rv, int off, int count) {
	int i;

	if (count <= 0
	 || off < 0
	 || (off + count * 1) > d->len)
		return NULL;

	if (rv == NULL) {
		if ((rv = (char *)malloc(sizeof(int) * (count + 1))) == NULL)
			return NULL;
	}

	for (i = 0; i < count; i++, off += 1) {
		rv[i] = (char)d->buf[off];
	}
	rv[i] = '\000';

	return rv;
}

/* Return a pointer to an array of ints containing data from 8 bits. */
/* If rv is NULL, the returned value will have been allocated, othewise */
/* the rv will be returned. Return NULL if out of range. */
static int *mkdata_get_8_ints(struct _mkdata *d, int *rv, int off, int count) {
	int i;

	if (count <= 0
	 || off < 0
	 || (off + count * 1) > d->len)
		return NULL;

	if (rv == NULL) {
		if ((rv = (int *)malloc(sizeof(int) * count)) == NULL)
			return NULL;
	}

	for (i = 0; i < count; i++, off += 1) {
		rv[i] = ((signed char *)d->buf)[off];
	}
	return rv;
}

/* Return a pointer to an array of ints containing data from unsigned 8 bits. */
/* If rv is NULL, the returned value will have been allocated, othewise */
/* the rv will be returned. Return NULL if out of range. */
static int *mkdata_get_u8_ints(struct _mkdata *d, int *rv, int off, int count) {
	int i;

	if (count <= 0
	 || off < 0
	 || (off + count * 1) > d->len)
		return NULL;

	if (rv == NULL) {
		if ((rv = (int *)malloc(sizeof(int) * count)) == NULL)
			return NULL;
	}

	for (i = 0; i < count; i++, off += 1) {
		rv[i] = d->buf[off];
	}
	return rv;
}

/* Return a pointer to an array of ints containing data from 16 bits. */
/* If rv is NULL, the returned value will have been allocated, othewise */
/* the rv will be returned. Return NULL if out of range. */
static int *mkdata_get_16_ints(struct _mkdata *d, int *rv, int off, int count) {
	int i;

	if (count <= 0
	 || off < 0
	 || (off + count * 2) > d->len)
		return NULL;

	if (rv == NULL) {
		if ((rv = (int *)malloc(sizeof(int) * count)) == NULL)
			return NULL;
	}

	for (i = 0; i < count; i++, off += 2) {
		rv[i] = buf2short(d->buf + off);
	}
	return rv;
}

/* Return a pointer to an array of ints containing data from unsigned 16 bits. */
/* If rv is NULL, the returned value will have been allocated, othewise */
/* the rv will be returned. Return NULL if out of range. */
static int *mkdata_get_u16_ints(struct _mkdata *d, int *rv, int off, int count) {
	int i;

	if (count <= 0
	 || off < 0
	 || (off + count * 2) > d->len)
		return NULL;

	if (rv == NULL) {
		if ((rv = (int *)malloc(sizeof(int) * count)) == NULL)
			return NULL;
	}

	for (i = 0; i < count; i++, off += 2) {
		rv[i] = buf2ushort(d->buf + off);
	}
	return rv;
}

/* Return a pointer to an array of ints containing data from 32 bits. */
/* If rv is NULL, the returned value will have been allocated, othewise */
/* the rv will be returned. Return NULL if out of range. */
static int *mkdata_get_32_ints(struct _mkdata *d, int *rv, int off, int count) {
	int i;

	if (count <= 0
	 || off < 0
	 || (off + count * 4) > d->len)
		return NULL;

	if (rv == NULL) {
		if ((rv = (int *)malloc(sizeof(int) * count)) == NULL)
			return NULL;
	}

	for (i = 0; i < count; i++, off += 4) {
		rv[i] = buf2int(d->buf + off);
	}
	return rv;
}

/* Return a pointer to an array of unsigned ints containing data from unsigned 32 bits. */
/* If rv is NULL, the returned value will have been allocated, othewise */
/* the rv will be returned. Return NULL if out of range. */
static unsigned int *mkdata_get_u32_uints(struct _mkdata *d, unsigned int *rv, int off, int count) {
	int i;

	if (count <= 0
	 || off < 0
	 || (off + count * 4) > d->len)
		return NULL;

	if (rv == NULL) {
		if ((rv = (unsigned int *)malloc(sizeof(unsigned int) * count)) == NULL)
			return NULL;
	}

	for (i = 0; i < count; i++, off += 4) {
		rv[i] = buf2uint(d->buf + off);
	}
	return rv;
}

/* Return a pointer to an array of doubles containing data from 32 bits. */
/* If rv is NULL, the returned value will have been allocated, othewise */
/* the rv will be returned. Return NULL if out of range or malloc failure. */
static double *mkdata_get_32_doubles(struct _mkdata *d, double *rv, int off, int count) {
	int i;

	if (count <= 0
	 || off < 0
	 || (off + count * 4) > d->len)
		return NULL;

	if (rv == NULL) {
		if ((rv = (double *)malloc(sizeof(double) * count)) == NULL)
			return NULL;
	}

	for (i = 0; i < count; i++, off += 4) {
		unsigned int val;
		val = buf2uint(d->buf + off);
		rv[i] = IEEE754todouble(val);
	}
	return rv;
}

/* Return a pointer to an array of doubles containing data from 32 bits, */
/* with the array filled in reverse order. */
/* If rv is NULL, the returned value will have been allocated, othewise */
/* the rv will be returned. Return NULL if out of range or malloc failure. */
static double *mkdata_rget_32_doubles(struct _mkdata *d, double *rv, int off, int count) {
	int i;

	if (count <= 0
	 || off < 0
	 || (off + count * 4) > d->len)
		return NULL;

	if (rv == NULL) {
		if ((rv = (double *)malloc(sizeof(double) * count)) == NULL)
			return NULL;
	}

	for (i = count-1; i >= 0; i--, off += 4) {
		unsigned int val;
		val = buf2uint(d->buf + off);
		rv[i] = IEEE754todouble(val);
	}
	return rv;
}


/* Destroy ourselves */
static void mkdata_del(mkdata *d) {
	del_a1log(d->log);		/* Unref it */
	free(d);
}

/* Constructor for mkdata */
mkdata *new_mkdata(munki *p, unsigned char *buf, int len) {
	mkdata *d;
	if ((d = (mkdata *)calloc(1, sizeof(mkdata))) == NULL) {
		a1loge(p->log, 1, "new_mkdata: malloc failed!\n");
		return NULL;
	}

	d->p = p;

	d->log = new_a1log_d(p->log);	/* Take reference */

	d->buf = buf;
	d->len = len;

	d->get_8_char      = mkdata_get_8_char;
	d->get_8_asciiz    = mkdata_get_8_asciiz;
	d->get_8_ints      = mkdata_get_8_ints;
	d->get_u8_ints     = mkdata_get_u8_ints;
	d->get_16_ints     = mkdata_get_16_ints;
	d->get_u16_ints    = mkdata_get_u16_ints;
	d->get_32_ints     = mkdata_get_32_ints;
	d->get_u32_uints   = mkdata_get_u32_uints;
	d->get_32_doubles  = mkdata_get_32_doubles;
	d->rget_32_doubles = mkdata_rget_32_doubles;

	d->del             = mkdata_del;

	return d;
}

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