
/* 
 * Argyll Color Correction System
 *
 * Author: Graeme W. Gill
 * Date:   20015
 *
 * Copyright 2006 - 2015 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.
 * 
 * Derived from i1pro_imp.c & munki_imp.c
 */

/*
 * A library for processing raw spectrometer values.
 *
 * Currently this is setup for the EX1 spectrometer,
 * but the longer term plan is to expand the functionality
 * so that it becomes more generic, and can replace a lot 
 * of common code in i1pro_imp.c & munki_imp.c.
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#if defined(UNIX)
# include <utime.h>
#else
# include <sys/utime.h>
#endif
#include <sys/stat.h>
#include <stdarg.h>
#ifndef SALONEINSTLIB
#include "copyright.h"
#include "aconfig.h"
#include "numlib.h"
#else	/* !SALONEINSTLIB */
#include "sa_config.h"
#include "numsup.h"
#endif /* !SALONEINSTLIB */
#include "plot.h"
#include "xspect.h"
#include "insttypes.h"
#include "conv.h"
#include "icoms.h"
#include "inst.h"
#include "rspec.h"

/* -------------------------------------------------- */
#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 */

/* -------------------------------------------------- */
/* Setup code */

/* Fit a wavelength polynomial to a set of mapping points */
// ~~~~9999

/* Completely clear an rspec_inf. */
void clear_rspec_inf(rspec_inf *inf) {
	memset(inf, 0, sizeof(rspec_inf));
}

/* Completely free contesnt of rspec_inf. */
void free_rspec_inf(rspec_inf *inf) {

	if (inf != NULL) {
		if (inf->straylight != NULL) {
			error("rspec_inf: help - don't know how to free straylight!");
		}
	
		if (inf->wlcal)
			free(inf->wlcal);
	
		if (inf->findex != NULL)
			free(inf->findex);
		if (inf->fnocoef != NULL)
			free(inf->fnocoef);
		if (inf->fcoef != NULL)
			free(inf->fcoef);
	
		if (inf->lin != NULL)
			free(inf->lin);
	
		if (inf->idark[0] != NULL)
			del_rspec(inf->idark[0]);
		if (inf->idark[1] != NULL)
			del_rspec(inf->idark[1]);
		
		if (inf->ecal != NULL)
			free(inf->ecal);
	
		clear_rspec_inf(inf);		/* In case it gets reused */
	}
}


/* return the number of samples for the given spectral type */
int rspec_typesize(rspec_inf *inf, rspec_type ty) {
	int no;
	if (ty == rspec_sensor)
		no = inf->nsen;
	else if (ty == rspec_raw)
		no = inf->nraw;
	else if (ty == rspec_wav)
		no = inf->nwav;
	else
		error("rspec_typesize type %d unknown",ty); 
	return no;
}

/* Compute the valid raw range from the calibration information */
void rspec_comp_raw_range_from_ecal(rspec_inf *inf) {
	int i;

	if (inf->ecaltype != rspec_raw)
		error("rspec_comp_raw_range_from_ecal: ecaltype not raw"); 

	for (i = 0; i < inf->nraw; i++) {
		if (inf->ecal[i] != 0.0) {
			inf->rawrange.off = i;
			break;
		}
	}
	if (i >= inf->nraw)
		error("rspec_comp_raw_range_from_ecal: ecal is zero");

	for (i = inf->rawrange.off; i < inf->nraw; i++) {
		if (inf->ecal[i] == 0.0) {
			break;
		}
	}
	inf->rawrange.num = i - inf->rawrange.off;
}

/* Convert a raw index to nm using polynomial */
double rspec_raw2nm(rspec_inf *inf, double rix) {
	int k;
	double wl;

	if (inf->nwlcal == 0)
		error("rspec_raw2nm: nwlcal == 0");

	/* Compute polinomial */
	for (wl = inf->wlcal[inf->nwlcal-1], k = inf->nwlcal-2; k >= 0; k--)
		wl = wl * rix + inf->wlcal[k];

	return wl;
}

/* Convert a cooked index to nm */
double rspec_wav2nm(rspec_inf *inf, double ix) {
	return inf->wl_short + ix * inf->wl_space;
}

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

/* Create a new rspec from scratch. */
/* Don't allocate samp if nmeas == 0 */
/* This always succeeds (i.e. application bombs if malloc fails) */
rspec *new_rspec(rspec_inf *inf, rspec_type ty, int nmeas) {
	rspec *p;
	int no;

	if ((p = (rspec *)calloc(1, sizeof(rspec))) == NULL) {
		error("Malloc failure in rspec()");
	}
	p->inf = inf;
	p->stype = ty;

	p->nmeas = nmeas;
	p->nsamp = rspec_typesize(inf, p->stype);
	if (nmeas > 0)
		p->samp = dmatrix(0, p->nmeas-1, 0, p->nsamp-1);

	return p;
}

/* Create a new rspec based on an existing prototype */ 
/* If nmeas == 0, create space for the same number or measurements */
rspec *new_rspec_proto(rspec *rs, int nmeas) {
	rspec *p;

	if ((p = (rspec *)calloc(1, sizeof(rspec))) == NULL) {
		error("Malloc failure in rspec()");
	}
	p->inf = rs->inf;
	p->stype = rs->stype;
	p->mtype = rs->mtype;
	p->state = rs->state;
	p->inttime = rs->inttime;

	if (nmeas == 0)
		p->nmeas = rs->nmeas;
	else
		p->nmeas = nmeas;
	p->nsamp = rs->nsamp;
	p->samp = dmatrix(0, p->nmeas-1, 0, p->nsamp-1);

	return p;
}

/* Create a new rspec by cloning an existing one */
rspec *new_rspec_clone(rspec *rs) {
	rspec *p;
	int i, j;

	if ((p = (rspec *)calloc(1, sizeof(rspec))) == NULL) {
		error("Malloc failure in rspec()");
	}
	p->inf = rs->inf;
	p->stype = rs->stype;
	p->mtype = rs->mtype;
	p->state = rs->state;
	p->inttime = rs->inttime;

	p->nmeas = rs->nmeas;
	p->nsamp = rs->nsamp;
	p->samp = dmatrix(0, p->nmeas-1, 0, p->nsamp-1);

	for (i = 0; i < p->nmeas; i++) {
		for (j = 0; j < p->nsamp; j++) {
			p->samp[i][j] = rs->samp[i][j];
		}
	}

	return p;
}

/* Free a rspec */
void del_rspec(rspec *p) {
	if (p != NULL) {
		if (p->samp != NULL)
			free_dmatrix(p->samp, 0, p->nmeas-1, 0, p->nsamp-1); 
		free(p);
	}
}

/* Plot the first rspec */
void plot_rspec1(rspec *p) {
	int i, no;
	double xx[RSPEC_MAXSAMP];
	double yy[RSPEC_MAXSAMP];

	no = rspec_typesize(p->inf, p->stype);
	
	for (i = 0; i < no; i++) {
		if (p->stype == rspec_wav)
			xx[i] = rspec_wav2nm(p->inf, (double)i);
		else
			xx[i] = (double)i;
		yy[i] = p->samp[0][i];
	}
	do_plot(xx, yy, NULL, NULL, no);
}

/* Plot the first rspec of 2 */
void plot_rspec2(rspec *p1, rspec *p2) {
	int i, no;
	double xx[RSPEC_MAXSAMP];
	double y1[RSPEC_MAXSAMP];
	double y2[RSPEC_MAXSAMP];

	// Should check p1 & p2 are compatible ??

	no = rspec_typesize(p1->inf, p1->stype);
	
	for (i = 0; i < no; i++) {
		if (p1->stype == rspec_wav)
			xx[i] = rspec_wav2nm(p1->inf, (double)i);
		else
			xx[i] = (double)i;
		y1[i] = p1->samp[0][i];
		y2[i] = p2->samp[0][i];
	}
	do_plot(xx, y1, y2, NULL, no);
}

void plot_ecal(rspec_inf *inf) {
	int i, no;
	double xx[RSPEC_MAXSAMP];
	double yy[RSPEC_MAXSAMP];

	no = rspec_typesize(inf, inf->ecaltype);
	
	for (i = 0; i < no; i++) {
		if (inf->ecaltype == rspec_wav)
			xx[i] = rspec_wav2nm(inf, (double)i);
		else
			xx[i] = (double)i;
		yy[i] = inf->ecal[i];
	}
	do_plot(xx, yy, NULL, NULL, no);
}


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

/* Return the largest value */
/* Optionally return the measurement and sample idex of that sample */
double largest_val_rspec(int *pmix, int *psix, rspec *raw) {
	double mx = -1e38;
	int mi = -1, mj = -1;
	int i, j;

	if (raw->nmeas <= 0)
		error("largest_val_rspec: raw has zero measurements");

	for (i = 0; i < raw->nmeas; i++) {
		for (j = 0; j < raw->nsamp; j++) {
			if (raw->samp[i][j] > mx) {
				mx = raw->samp[i][j];
				mi = i;
				mj = j;
			}
		}
	}
	if (pmix != NULL)
		*pmix = mi;
	if (psix != NULL)
		*psix = mj;

	return mx;
}

/* return a raw rspec from a sensor rspec */
/* (This does not make any adjustments to the values) */
rspec *extract_raw_from_sensor_rspec(rspec *sens) {
	rspec *raw;
	int off, i, j;

	if (sens->stype != rspec_sensor)
		error("extract_raw_from_sensor_rspec: input is not sensor type");

	raw = new_rspec(sens->inf, rspec_raw, sens->nmeas);

	raw->mtype = sens->mtype;
	raw->state = sens->state;
	raw->inttime = sens->inttime;

	off = sens->inf->lightrange.off;
	for (i = 0; i < raw->nmeas; i++) {
		for (j = 0; j < raw->nsamp; j++) {
			raw->samp[i][j] = sens->samp[i][off + j];
		}
	}

	return raw;
}

/* Return an interpolated dark reference value from idark */
double ex1_interp_idark_val(rspec_inf *inf, int mix, int six, double inttime) {
	double idv;
	double w0, w1;
	int i, j;

	w1 = (inttime - inf->idark[0]->inttime)/(inf->idark[1]->inttime - inf->idark[0]->inttime);
	w0 = 1.0 - w1;

	idv = w0 * inf->idark[0]->samp[mix][six] + w1 * inf->idark[1]->samp[mix][six];

	return idv;
}

/* Return an interpolated dark reference from idark */
rspec *ex1_interp_idark(rspec_inf *inf, double inttime) {
	double w0, w1;
	int i, j;
	rspec *dark;

	w1 = (inttime - inf->idark[0]->inttime)/(inf->idark[1]->inttime - inf->idark[0]->inttime);
	w0 = 1.0 - w1;

	dark = new_rspec_proto(inf->idark[0], 0);

	for (i = 0; i < inf->idark[0]->nmeas; i++) {
		for (j = 0; j < inf->idark[0]->nsamp; j++)
			dark->samp[i][j] = w0 * inf->idark[0]->samp[i][j] + w1 * inf->idark[1]->samp[i][j];
	}

	return dark;
}

/* Subtract the adaptive black */
void subtract_idark_rspec(rspec *raw) {
	rspec_inf *inf = raw->inf;
	int i, j;
	rspec *dark;

	if (raw->state & rspec_dcal)
		error("subtract_idark_rspec: already done");

	if (raw->stype != inf->idark[0]->stype)
		error("subtract_idark_rspect: idark does not match rspec type");

	dark = ex1_interp_idark(inf, raw->inttime);

	for (i = 0; i < raw->nmeas; i++) {
		for (j = 0; j < raw->nsamp; j++) {
			raw->samp[i][j] -= dark->samp[0][j];
		}
	}

	raw->state |= rspec_dcal;
}

/* Apply non-linearity */
double linearize_val_rspec(rspec_inf *inf, double ival) {
	double oval = ival;
	int k;

	if (ival >= 0.0) {
		for (oval = inf->lin[inf->nlin-1], k = inf->nlin-2; k >= 0; k--) {
			oval = oval * ival + inf->lin[k];
	 	}

		if (inf->lindiv)		/* EX1 divides */
			oval = ival/oval;
	}
	return oval;
}

/* Invert non-linearity. */
/* Since the linearisation is nearly a straight line, */
/* a simple Newton inversion will suffice. */
double inv_linearize_val_rspec(rspec_inf *inf, double targv) {
	double oval, ival = targv, del = 100.0;
	int i, k;

	for (i = 0; i < 200 && fabs(del) > 1e-7; i++) {
		for (oval = inf->lin[inf->nlin-1], k = inf->nlin-2; k >= 0; k--)
			oval = oval * ival + inf->lin[k];
		if (inf->lindiv)		/* EX1 divides */
			oval = ival/oval;

		del = (targv - oval);
		ival += 0.99 * del;
	}
	return ival;
}

/* Correct non-linearity */
void linearize_rspec(rspec *raw) {
	rspec_inf *inf = raw->inf;
	int i, j;
	rspec *dark;

	if (raw->state & rspec_lin)
		error("linearize_rspec: already done");

	if (raw->state & rspec_int)
		error("linearize_rspec: can't be integration time adjusted");

	if (!(raw->state & rspec_dcal))
		error("linearize_rspec: needs black subtract");

	if (inf->nlin > 0) {
		for (i = 0; i < raw->nmeas; i++) {
			for (j = 0; j < raw->nsamp; j++) {
				raw->samp[i][j] = linearize_val_rspec(inf, raw->samp[i][j]);
			}
		}
	}
	raw->state |= rspec_lin;
}

/* Apply the emsissive calibration */
void emis_calibrate_rspec(rspec *raw) {
	rspec_inf *inf = raw->inf;
	int i, j;

	if (raw->state & rspec_cal)
		error("emis_calibrate_rspec: already done");

	if (raw->stype != raw->inf->ecaltype)
		error("emis_calibrate_rspec: ecaltype does not match rspec type");

	for (i = 0; i < raw->nmeas; i++) {
		for (j = 0; j < raw->nsamp; j++) {
			raw->samp[i][j] *= inf->ecal[j];
		}
	}
	raw->state |= rspec_cal;
}

/* Scale to the integration time */
void inttime_calibrate_rspec(rspec *raw) {
	rspec_inf *inf = raw->inf;
	int i, j;

	if (raw->state & rspec_int)
		error("inttime_calibrate_rspec: already done");

	for (i = 0; i < raw->nmeas; i++) {
		for (j = 0; j < raw->nsamp; j++) {
			raw->samp[i][j] /= raw->inttime;
		}
	}

	raw->inttime = 1.0;

	raw->state |= rspec_int;
}

/* return a wav rspec from a raw rspec */
/* (This does not make any adjustments to the values) */
rspec *convert_wav_from_raw_rspec(rspec *raw) {
	rspec_inf *inf = raw->inf;
	rspec *wav;
	int cx, sx, i, j, k;

	if (raw->stype != rspec_raw)
		error("extract_raw_from_sensor_rspec: input is not raw type");

	wav = new_rspec(raw->inf, rspec_wav, raw->nmeas);

	wav->mtype = raw->mtype;
	wav->state = raw->state;
	wav->inttime = raw->inttime;

	for (i = 0; i < wav->nmeas; i++) {				/* For each measurement */
		for (cx = j = 0; j < inf->nwav; j++) {		/* For each wav sample */
			double oval = 0.0;
	
			sx = inf->findex[j];		/* Starting index */
			for (k = 0; k < inf->fnocoef[j]; k++, cx++, sx++)	/* For each matrix value */
				oval += inf->fcoef[cx] * raw->samp[i][sx];
			wav->samp[i][j] = oval;
		}
	}

	return wav;
}

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


/* Filter code in i1pro_imp is in:

	i1pro_compute_wav_filters()		X-Rite way
	i1pro_create_hr()				Using gausian

 */

/* Resampling kernels. (There are more in i1pro_imp.c) */

/* They aren't expected to be unity area, as they will be */
/* normalized anyway. */
/* wi is the width of the filter */

static double triangle(double wi, double x) {
	double y = 0.0;

	x = fabs(x/wi);
	y = 1.0 - x;
	if (y < 0.0)
		y = 0.0;

	return y;
}

static double gausian(double wi, double x) {
	double y = 0.0;

	wi = wi/(sqrt(2.0 * log(2.0)));	/* Convert width at half max to std. dev. */
    x = x/wi;
    y = exp(-(x * x));											/* Center at 1.0 */

	return y;
}

static double lanczos2(double wi, double x) {
	double y = 0.0;

	wi *= 1.05;			// Improves smoothness. Why ?

	x = fabs(1.0 * x/wi);
	if (x >= 2.0)
		return 0.0; 
	if (x < 1e-6)
		return 1.0;
	y = sin(DBL_PI * x)/(DBL_PI * x) * sin(DBL_PI * x/2.0)/(DBL_PI * x/2.0);

	return y;
}

static double lanczos3(double wi, double x) {
	double y = 0.0;

	x = fabs(1.0 * x/wi);
	if (x >= 3.0)
		return 0.0; 
	if (x < 1e-6)
		return 1.0;
	y = sin(DBL_PI * x)/(DBL_PI * x) * sin(DBL_PI * x/3.0)/(DBL_PI * x/3.0);

	return y;
}

static double cubicspline(double wi, double x) {
	double y = 0.0;
	double xx = x;
	double bb, cc;

	xx = fabs(1.0 * x/wi);

//	bb = cc = 1.0/3.0;		/* Mitchell */
	bb = 0.5;
	cc = 0.5;

	if (xx < 1.0) {
		y = ( 12.0 -  9.0 * bb - 6.0 * cc) * xx * xx * xx
		  + (-18.0 + 12.0 * bb + 6.0 * cc) * xx * xx
		  + (  6.0 -  2.0 * bb);
		y /= (6.0 - 2.0 * bb);
	} else if (xx < 2.0) {
		y = ( -1.0 * bb -  6.0 * cc) * xx * xx * xx
		  + (  6.0 * bb + 30.0 * cc) * xx * xx
		  + (-12.0 * bb - 48.0 * cc) * xx 
		  + (  8.0 * bb + 24.0 * cc);
		y /= (6.0 - 2.0 * bb);
	} else {
		y = 0.0;
	}

	return y;
}

/* Create the wavelength resampling filters */
void rspec_make_resample_filters(rspec_inf *inf) {
	double twidth = inf->wl_space;
	double rawspace;				/* Average raw band spacing wl */
	double fshmax;					/* filter shape max wavelength from center */
	double finc;					/* Integration step size */
	int maxcoeffs;					/* Maximum coefficients per filter */
	int    **coeff_ix;				/* [band][coef] Raw index */
	double **coeff_we;				/* [band][coef] Weighting */
	double (*kernel)(double wi, double x) = NULL;	/* Filter kernel */
	int xcount;
	int i, j, k;
	
	if (inf->ktype == rspec_triangle)
		kernel = triangle; 
	else if (inf->ktype == rspec_gausian)
		kernel = gausian; 
	else if (inf->ktype == rspec_lanczos2)
		kernel = lanczos2; 
	else if (inf->ktype == rspec_lanczos3)
		kernel = lanczos3; 
	else if (inf->ktype == rspec_cubicspline)
		kernel = cubicspline; 
	else
		error("rspec_make_resample_filters: unknown kernel %d",inf->ktype); 
	
#ifdef NEVER		// Check kernel sums to 1.0
{
	double x, y;

	for (x = 0.0; x < 5.0; x += 0.1) {
		y = kernel(1.0, x - 4.0)
		  + kernel(1.0, x - 3.0)
		  + kernel(1.0, x - 2.0)
		  + kernel(1.0, x - 1.0)
		  + kernel(1.0, x)
		  + kernel(1.0, x + 1.0)
		  + kernel(1.0, x + 2.0);
		  + kernel(1.0, x + 3.0);
		  + kernel(1.0, x + 4.0);

		printf("Offset %f sum %f\n",x,y);
	}

}
#endif 	// NEVER

	/* Aproximate raw value spacing in nm */
	rawspace = (inf->wl_long - inf->wl_short)/inf->rawrange.num;
//printf("~1 rawspace = %f\n",rawspace);

	/* Figure the extent of the filter kernel. We assume they */
	/* all have a finite extent. */
	for (fshmax = 50.0; fshmax >= 0.0; fshmax -= 0.01) {
		if (fabs(kernel(twidth, fshmax)) > 1e-6) {
			fshmax += 0.01;
			break;
		}
	}
//printf("~1 fshmax = %f\n",fshmax);

	if (fshmax <= 0.0)
		error("rspec_make_resample_filters: fshmax search failed\n");

	a1logd(inf->log, 4,"rspec_make_resample_filters: fshmax = %f\n",fshmax);

	/* Figure number of raw samples over kernel extent. */
	/* (Allow generous factor for non-linearity) */
	maxcoeffs = (int)floor(2.0 * 1.4 * fshmax/rawspace + 3.0);

	a1logd(inf->log, 4,"rspec_make_resample_filters: maxcoeffs = %d\n",maxcoeffs);

	/* Figure out integration step size */
#ifdef FAST_HIGH_RES_SETUP
	finc = twidth/50.0;
	if (rawspace/finc < 10.0)
		finc = rawspace/10.0;
#else
	finc = twidth/15.0;
	if (rawspace/finc < 4.0)
		finc = rawspace/4.0;
#endif

	a1logd(inf->log, 4,"rspec_make_resample_filters: integration step = %f\n",finc);

	if (inf->fnocoef != NULL)
		free(inf->fnocoef);
	if ((inf->fnocoef = (int *)calloc(inf->nwav, sizeof(int))) == NULL)
		error("rspec_make_resample_filters: malloc failure");

	/* Space to build filter coeficients */
	coeff_ix = imatrix(0, inf->nwav-1, 0, maxcoeffs-1);
	coeff_we = dmatrix(0, inf->nwav-1, 0, maxcoeffs-1);

	/* For all the usable raw bands */
	for (i = inf->rawrange.off+1; i < (inf->rawrange.off+inf->rawrange.num-1); i++) {
		double w1, wl, w2;

		/* Translate CCD center and boundaries to calibrated wavelength */
		wl = rspec_raw2nm(inf, (double)i);
		w1 = rspec_raw2nm(inf, (double)i - 0.5);
		w2 = rspec_raw2nm(inf, (double)i + 0.5);

//		printf("~1 CCD %d, w1 %f, wl %f, w2 %f\n",i,w1,wl,w2);

		/* For each output filter */
		for (j = 0; j < inf->nwav; j++) {
			double cwl, rwl;		/* center, relative wavelegth */
			double we;

			cwl = rspec_wav2nm(inf, (double)j);
			rwl = wl - cwl;			/* raw relative wavelength to filter */

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

			/* Integrate in finc nm increments from filter shape */
			/* using triangular integration. */
			{
				int nn;
				double lw, ll;

				nn = (int)(fabs(w2 - w1)/finc + 0.5);		/* Number to integrate over */
		
				lw = w1;				/* start at lower boundary of CCD cell */
				ll = kernel(twidth, w1 - cwl);
				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) * fabs(w2 - w1);	/* wl to sample */
					cl = kernel(twidth, cw - cwl);
					we += 0.5 * (cl + ll) * fabs(lw - cw);			/* Area under triangle */
					ll = cl;
					lw = cw;
				}
			}

			if (inf->fnocoef[j] >= maxcoeffs)
				error("rspec_make_resample_filters: run out of high res filter space\n");

			coeff_ix[j][inf->fnocoef[j]] = i;
			coeff_we[j][inf->fnocoef[j]++] = we; 
//			printf("~1 filter %d, cwl %f, rwl %f, ix %d, we %f, nocoefs %d\n",j,cwl,rwl,i,we,info->fnocoef[j]);
		}
	}

	/* Convert hires filters into runtime format: */

	/* Allocate or reallocate high res filter tables */
	if (inf->findex != NULL)
		free(inf->findex);
	if (inf->fcoef != NULL)
		free(inf->fcoef);

	if ((inf->findex = (int *)calloc(inf->nraw, sizeof(int))) == NULL)
		error("rspec_make_resample_filters: malloc index failed!\n");

	/* Count the total number of coefficients */
	for (xcount = j = 0; j < inf->nwav; j++) {
		inf->findex[j] = coeff_ix[j][0];		/* raw starting index */ 
		xcount += inf->fnocoef[j];
	}

//printf("~1 total coefs = %d\n",xcount);

	/* Allocate space for them */
	if ((inf->fcoef = (double *)calloc(xcount, sizeof(double))) == NULL)
		error("rspec_make_resample_filters: malloc index failed!\n");

	/* Normalize the weight * nm to 1.0, and pack them into the run-time format */
	for (i = j = 0; j < inf->nwav; j++) {
		int sx;
		double rwi, twe = 0.0;

		sx = inf->findex[j];		/* raw starting index */

		for (k = 0; k < inf->fnocoef[j]; sx++, k++) {
			/* Width of raw band in nm */
			rwi = fabs(rspec_raw2nm(inf, (double)sx - 0.5)
			         - rspec_raw2nm(inf, (double)sx + 0.5));
			twe += rwi * coeff_we[j][k];
		}

		if (twe > 0.0)
			twe = 1.0/twe;
		else
			twe = 1.0;

//		printf("Output %d, nocoefs %d, norm weight %f:\n",j,inf->fnocoef[j],twe);

		for (k = 0; k < inf->fnocoef[j]; k++, i++) {
			inf->fcoef[i] = coeff_we[j][k] * twe;
//			printf("  coef %d packed %d from raw %d val %f\n",k,i,inf->findex[j]+k,inf->fcoef[i]);
		}
	}

	free_imatrix(coeff_ix, 0, inf->nwav-1, 0, maxcoeffs-1);
	free_dmatrix(coeff_we, 0, inf->nwav-1, 0, maxcoeffs-1);
}

//printf("~1 line %d\n",__LINE__);

/* Plot the wave resampling filters */
void plot_resample_filters(rspec_inf *inf) {
	double *xx, *ss;
	double **yy;
	int i, j, k, sx;

//printf("~1 nraw = %d\n",inf->nraw);

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

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

		sx = inf->findex[j];		/* raw starting index */
//printf("Output %d raw sx %d\n",j,sx);

		/* For each matrix value */
		for (k = 0; k < inf->fnocoef[j]; k++, sx++, i++) {
			yy[5][sx] += 0.5 * inf->fcoef[i];
			yy[j % 5][sx] = inf->fcoef[i];
//printf(" filter %d six %d weight = %e\n",k,sx,inf->fcoef[i]);
		}
	}

	printf("Wavelength re-sampling curves:\n");
//	do_plot6(xx, yy[0], yy[1], yy[2], yy[3], yy[4], yy[5], 150);
	do_plot6(xx, yy[0], yy[1], yy[2], yy[3], yy[4], yy[5], inf->nraw);
	free_dvector(xx, 0, inf->nraw-1);
	free_dmatrix(yy, 0, 2, 0, inf->nraw-1);
}

/* ================================================== */
/* Calibration file support */

/* Open the file. nz wr for write mode, else read */
/* Return nz on error */
int calf_open(calf *x, a1log *log, char *fname, int wr) {
	char nmode[10];
	char cal_name[200];
	char **cal_paths = NULL;
	int no_paths = 0;

	memset((void *)x, 0, sizeof(calf));
	x->log = log;

	if (wr)
		strcpy(nmode, "w");
	else
		strcpy(nmode, "r");

#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

	/* Create the file name */
	if (wr)
		sprintf(cal_name, "ArgyllCMS/%s", fname);
	else
		sprintf(cal_name, "ArgyllCMS/%s" SSEPS "color/%s", fname, fname);
	if ((no_paths = xdg_bds(NULL, &cal_paths, xdg_cache, xdg_write, xdg_user, cal_name)) < 1) {
		a1logd(x->log,1,"calf_open: xdg_bds returned no paths\n");
		return 1;
	}

	a1logd(x->log,2,"calf_open: %s file '%s'\n",cal_paths[0], wr ? "saving to" : "restoring from");

	/* Check the last modification time */
	if (!wr) {
		struct sys_stat sbuf;

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

	if ((wr && create_parent_directories(cal_paths[0]))
	 || (x->fp = fopen(cal_paths[0], nmode)) == NULL) {
		a1logd(x->log,2,"calf_open: failed to open file for %s\n",wr ? "writing" : "reading");
		xdg_free(cal_paths, no_paths);
		return 1;
	}
	xdg_free(cal_paths, no_paths);

	a1logd(x->log,2,"calf_open: suceeded\n");

	return 0;
}

/* Update the modification time */
/* Return nz on error */
int calf_touch(a1log *log, char *fname) {
	char cal_name[200];
	char **cal_paths = NULL;
	int no_paths = 0;
	int rv;

	/* Locate the file name */
	sprintf(cal_name, "ArgyllCMS/%s" SSEPS "color/%s", fname, fname);

	if ((no_paths = xdg_bds(NULL, &cal_paths, xdg_cache, xdg_read, xdg_user, cal_name)) < 1) {
		a1logd(log,2,"calf_touch: xdg_bds failed to locate file'\n");
		return 1;
	}

	a1logd(log,2,"calf_touch: touching file '%s'\n",cal_paths[0]);

	if ((rv = sys_utime(cal_paths[0], NULL)) != 0) {
		a1logd(log,2,"calf_touch: failed with %d\n",rv);
		xdg_free(cal_paths, no_paths);
		return 1;
	}
	xdg_free(cal_paths, no_paths);

	return 0;
}

/* Rewind and reset for another read */
void calf_rewind(calf *x) {
	x->ef = 0;
	x->chsum = 0;
	x->nbytes = 0;
	rewind(x->fp);
}

/* Close the file and free any memory */
/* return nz on error */
int calf_done(calf *x) {
	int rv = 0;

	if (x->fp != NULL) {
		if (fclose(x->fp)) {
			a1logd(x->log,2,"calf_done: closing file failed\n");
			rv = 1;
		}
	}

	if (x->buf != NULL)
		free(x->buf);
	x->buf = NULL;
	return rv;
}

static void sizebuf(calf *x, size_t size) {
	if (x->bufsz < size)
		x->buf = realloc(x->buf, size);
	if (x->buf == NULL)
		error("calf: sizebuf malloc failed");
}

static void update_chsum(calf *x, unsigned char *p, int nn) {
	int i;
	for (i = 0; i < nn; i++, p++)
		x->chsum = ((x->chsum << 13) | (x->chsum >> (32-13))) + *p;
	x->nbytes += nn;
}

/* Write an array of ints to the file. Set the error flag to nz on error */
void calf_wints(calf *x, int *dp, int n) {
	if (x->ef)
		return;

	if (fwrite((void *)dp, sizeof(int), n, x->fp) != n) {
		x->ef = 1;
		a1logd(x->log,2,"calf_wints: write failed for %d ints at offset %d\n",n,x->nbytes);
	} else {
		update_chsum(x, (unsigned char *)dp, sizeof(int) * n);
	}
}

/* Write an array of doubles to the file. Set the error flag to nz on error */
void calf_wdoubles(calf *x, double *dp, int n) {
	if (x->ef)
		return;

	if (fwrite((void *)dp, sizeof(double), n, x->fp) != n) {
		x->ef = 1;
		a1logd(x->log,2,"calf_wdoubles: write failed for %d doubles at offset %d\n",n,x->nbytes);
	} else {
		update_chsum(x, (unsigned char *)dp, sizeof(double) * n);
	}
}

/* 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) */
void calf_wtime_ts(calf *x, time_t *dp, int n) {
	if (x->ef)
		return;

	if (fwrite((void *)dp, sizeof(time_t), n, x->fp) != n) {
		x->ef = 1;
		a1logd(x->log,2,"calf_wtime_ts: write failed for %d time_ts at offset %d\n",n,x->nbytes);
	} else {
		update_chsum(x, (unsigned char *)dp, sizeof(time_t) * n);
	}
}

/* Write a zero terminated string */
void calf_wstrz(calf *x, char *dp) {
	int n;

	if (x->ef)
		return;

	n = strlen(dp) + 1;

	calf_wints(x, &n, 1);

	if (fwrite((void *)dp, sizeof(char), n, x->fp) != n) {
		x->ef = 1;
		a1logd(x->log,2,"calf_wstrz: write failed for %d long string at offset %d\n",n,x->nbytes);
	} else {
		update_chsum(x, (unsigned char *)dp, sizeof(char) * n);
	}
}


/* Read an array of ints from the file. Set the error flag to nz on error */
/* Always read (ignore rd flag) */
void calf_rints2(calf *x, int *dp, int n) {
	if (x->ef)
		return;

	if (fread((void *)dp, sizeof(int), n, x->fp) != n) {
		x->ef = 1;
		a1logd(x->log,2,"calf_rints2: read failed for %d ints at offset %d\n",n,x->nbytes);
	} else {
		update_chsum(x, (unsigned char *)dp, sizeof(int) * n);
	}
}


/* Read an array of ints from the file. Set the error flag to nz on error */
void calf_rints(calf *x, int *dp, int n) {
	size_t nbytes = n * sizeof(int);
	void *dest = (void *)dp;

	if (x->ef)
		return;

	if (x->rd == 0) {		/* Dummy read */
		sizebuf(x, nbytes);
		dest = x->buf;
	}

	if (fread(dest, 1, nbytes, x->fp) != nbytes) {
		x->ef = 1;
		a1logd(x->log,2,"calf_rints: read failed for %d ints at offset %d\n",n,x->nbytes);
	} else {
		update_chsum(x, dest, nbytes);
	}
}

/* Read an array of doubles from the file. Set the error flag to nz on error */
void calf_rdoubles(calf *x, double *dp, int n) {
	size_t nbytes = n * sizeof(double);
	void *dest = (void *)dp;

	if (x->ef)
		return;

	if (x->rd == 0) {		/* Dummy read */
		sizebuf(x, nbytes);
		dest = x->buf;
	}

	if (fread(dest, 1, nbytes, x->fp) != nbytes) {
		x->ef = 1;
		a1logd(x->log,2,"calf_rdoubles: read failed for %d ints at offset %d\n",n,x->nbytes);
	} else {
		update_chsum(x, dest, nbytes);
	}
}

/* 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) */
void calf_rtime_ts(calf *x, time_t *dp, int n) {
	size_t nbytes = n * sizeof(time_t);
	void *dest = (void *)dp;

	if (x->ef)
		return;

	if (x->rd == 0) {		/* Dummy read */
		sizebuf(x, nbytes);
		dest = x->buf;
	}

	if (fread(dest, 1, nbytes, x->fp) != nbytes) {
		x->ef = 1;
		a1logd(x->log,2,"calf_rtime_ts: read failed for %d ints at offset %d\n",n,x->nbytes);
	} else {
		update_chsum(x, dest, nbytes);
	}
}

/* Read a zero terminated string. */
void calf_rstrz(calf *x, char **dp) {
	int n;
	size_t nbytes = 0;
	char *dest = NULL;

	if (x->ef)
		return;

	calf_rints2(x, &n, 1);
	nbytes = sizeof(char) * n;

	if (x->ef || n == 0)
		return;

	if (x->rd != 0) {	/* Reading for real */
		if (*dp != NULL)
			free(*dp);
		if ((*dp = dest = malloc(nbytes)) == NULL)
			error("calf: calf_rstrz malloc failed");
	} else {
		sizebuf(x, nbytes);
		dest = x->buf;
	}
	if (fread(dest, 1, nbytes, x->fp) != nbytes) {
		x->ef = 1;
		a1logd(x->log,2,"calf_rstrz: read failed for %d long string at offset %d\n",n,x->nbytes);
	} else {
		update_chsum(x, (unsigned char*)dest, nbytes);
	}
}

void calf_rstrz2(calf *x, char **dp) {
	int rd = x->rd;
	x->rd = 1;
	calf_rstrz(x, dp);
	x->rd = rd;
}

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

/* Save a rspec to a calibration file */
void calf_wrspec(calf *x, rspec *s) {
	int i;

	if (x->ef)
		return;

	calf_wints(x, (int *)&s->stype, 1);
	calf_wints(x, (int *)&s->mtype, 1);
	calf_wints(x, (int *)&s->state, 1);
	calf_wdoubles(x, &s->inttime, 1);
	calf_wints(x, &s->nmeas, 1);
	calf_wints(x, &s->nsamp, 1);

	for (i = 0; i < s->nmeas; i++) {
		calf_wdoubles(x, s->samp[i], s->nsamp);
	}
}

/* Create a rspec from a calibration file */
void calf_rrspec(calf *x, rspec **dp, rspec_inf *inf) {
	rspec *s, dumy;
	int no, i;

	if (x->ef)
		return;

	if (x->rd != 0) {
		if (*dp != NULL)
			del_rspec(*dp);
		*dp = s = new_rspec(inf, rspec_sensor, 0);	
	} else {
		s = &dumy;
	}
	
	calf_rints2(x, (int *)&s->stype, 1);
	calf_rints2(x, (int *)&s->mtype, 1);
	calf_rints2(x, (int *)&s->state, 1);
	calf_rdoubles(x, &s->inttime, 1);
	calf_rints2(x, &s->nmeas, 1);
	calf_rints2(x, &s->nsamp, 1);

	/* Sanity check. */
	no = rspec_typesize(inf, s->stype);
	if (no != s->nsamp) {
		a1logd(inf->log, 4,"calf_rrspec: unexpected nsamp %d (expect %d)\n",s->nsamp,no);
		x->ef = 1;
		return;
	}

	if (x->rd != 0) {
		s->samp = dmatrix(0, s->nmeas-1, 0, s->nsamp-1);
		for (i = 0; i < s->nmeas; i++) {
			calf_rdoubles(x, s->samp[i], s->nsamp);
		}
	} else {
		for (i = 0; i < s->nmeas; i++) {
			calf_rdoubles(x, NULL, s->nsamp);
		}
	}
}

