
/*
 * Raster Color Target Scan Input module
 * This is the core chart recognition code.
 * 
 * Author: Graeme Gill
 *
 * Copyright 1995 - 2008 Graeme W. Gill, All right reserved.
 * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
 * see the License.txt file for licencing details.
 */

/*
 * To Do: 
 *        Fix sboxes parameters/digitization to fix "droop" in box areas.
 *        Scale parameters with image size.
 *        To handle high res, introduce automatic sub-sampler.
 *		  Change reference parser to make it more forgiving - use cgats parser ?
 */

#undef DEBUG

#define VERSION "1.0"

/* Behaviour defines */
#undef DIAGN				/* Allow diagonal connectivity of groups */
#define AA_LINES			/* Plot diagnostics using anti-aliased lines */

#define MATCHCC 0.3			/* Match correlation threshold - reject any match under this */
							/* (Might want to be able to override this in command line) */

#define ALT_ROT_TH 0.7		/* Correlation threshold of alternate rotations to be greater than this */

#define TH (20.0 * 20.0)	/* Initial color change threshhold */

#undef DBG
#define dbgo stdout
#define DBG(aaa) fprintf aaa, fflush(dbgo)

#include <stdio.h>
/* #include <fcntl.h> */		/* In case DOS binary stuff is needed */
#include <string.h>
#include <math.h>

#include <stdlib.h>
#include <sys/stat.h>
/*  #include <fname.h> */

#include "numlib.h"
#include "scanrd_.h"

/* ------------------------------------------------- */
/* Implementations of public functions */
static void free_scanrd(scanrd *s);
static int scanrd_reset(scanrd *s);
static int scanrd_read(scanrd *ps, char *id, double *P, double *mP,
                       double *sdP, int *cnt);
static unsigned int scanrd_error(scanrd *s, char **errm);

/* Forward internal function declaration */
static scanrd_ *new_scanrd(int flags, int verb, double gammav,
	int (*write_line)(void *ddata, int y, char *src), void *ddata,
	int w, int h, int d, int td, int p,
	int (*read_line)(void *fdata, int y, char *dst), void *fdata,
	char *refname);
static int read_input(scanrd_ *s);
static int calc_lines(scanrd_ *s);
static int show_lines(scanrd_ *s);
static int calc_perspective(scanrd_ *s);
static int calc_rotation(scanrd_ *s);
static int calc_elists(scanrd_ *s, int ref);
static int write_elists(scanrd_ *s);
static int read_relists(scanrd_ *s);
static int do_match(scanrd_ *s);
static int compute_ptrans(scanrd_ *s);
static int compute_man_ptrans(scanrd_ *s, double *sfids);
static int improve_match(scanrd_ *s);
static int setup_sboxes(scanrd_ *s);
static int do_value_scan(scanrd_ *s);
static int compute_xcc(scanrd_ *s);
//static int restore_best(scanrd_ *s);
static int show_sbox(scanrd_ *s);
static int show_groups(scanrd_ *s);
static int scanrd_write_diag(scanrd_ *s);
static void toRGB(unsigned char *dst, unsigned char *src, int depth, int bpp);
static void XYZ2Lab(double *out, double *in);
static void pval2Lab(double *out, double *in, int depth);
/* ------------------------------------------------- */

/* Read in a chart, and either create a reference or make values available, */
/* by using reset() and read() to get values read */
scanrd *do_scanrd(
int flags,			/* option flags */
int verb,			/* verbosity level */

double gammav,		/* Apprimate gamma encoding of image (0.0 = default 2.2) */
double *sfid,		/* Specified four fiducials x1, y1 .. x4, y4, NULL if auto recognition */
					/* Typical clockwise from top left */

int w, int h, 		/* Width and Height of input raster in pixels */
int d, int td, int p,		/* Useful plane depth, Total depth, Bit presision of input pixels */
int (*read_line)(void *fdata, int y, char *dst),	/* Read RGB line of source file */
void *fdata,		/* Opaque data for read_line */

char *refname,		/* reference file name */

int (*write_line)(void *ddata, int y, char *src),	/* Write RGB line of diag file */
void *ddata			/* Opaque data for write_line */
) {
	scanrd_ *s;

	/* allocate the basic object */
	if (verb >= 2)
		DBG((dbgo,"About to allocate scanrd_ object\n"));
	if ((s = new_scanrd(flags, verb, gammav, write_line, ddata, w, h, d, td, p, read_line, fdata, refname)) == NULL)
		return NULL;

	if (s->errv != 0)	/* Some other error from new_scanrd() */
		return (scanrd *)s;

	if (s->verb >= 2)
		DBG((dbgo,"About to read input tiff file and discover groups\n"));
	if (read_input(s))
		goto sierr;			/* Error */

	if (s->flags & SI_SHOW_GROUPS)
		if (show_groups(s))
			goto sierr;		/* Error */
	
	if (s->verb >= 2)
		DBG((dbgo,"About to calculate edge lines\n"));
	if (calc_lines(s))
		goto sierr;			/* Error */
	if (s->verb >= 2)
		DBG((dbgo,"%d useful edges out of %d\n",s->novlines, s->noslines));

	if (s->flags & SI_PERSPECTIVE) {
		if (s->verb >= 2)
			DBG((dbgo,"About to calculate perspective correction\n"));
		if (calc_perspective(s)) {
			if (s->flags & SI_SHOW_LINES) {
				s->flags &= ~SI_SHOW_PERS;	/* Calc perspective failed! */
				s->flags &= ~SI_SHOW_ROT;	/* Calc rotation not done! */
				show_lines(s);
			}
			goto sierr;			/* Error */
		}
	}

	if (s->verb >= 2)
		DBG((dbgo,"About to calculate rotation\n"));
	if (calc_rotation(s)) {
		if (s->flags & SI_SHOW_LINES) {
			s->flags &= ~SI_SHOW_ROT;	/* Calc rotation failed! */
			show_lines(s);
		}
		goto sierr;			/* Error */
	}

	if (s->flags & SI_BUILD_REF) { /* If generating a chart reference file */
		/* Calculate the edge lists and write it to the file */
		if (s->verb >= 2)
			DBG((dbgo,"About to build feature information\n"));
		if (calc_elists(s, 1))		/* reference */
			goto sierr;		/* Error */

		if (s->verb >= 2)
			DBG((dbgo,"About to write feature reference information\n"));
		if (write_elists(s))
			goto sierr;		/* Error */
	} else {
		/* If we are matching to the reference and generating an output data file */
		int rv;

		/* Calculate the edge lists read for a match */
		if (s->verb >= 2)
			DBG((dbgo,"About to calculate feature information\n"));
		if (calc_elists(s, 0))		/* match */
			goto sierr;		/* Error */

		if (s->verb >= 2)
			DBG((dbgo,"About to read reference feature information\n"));
		if (read_relists(s))
			goto sierr;		/* Error */
		if (s->verb >= 2)
			DBG((dbgo,"Read of chart reference file succeeded\n"));

		if (sfid != NULL) {		/* Manual matching */
			if (s->verb >= 2)
				DBG((dbgo,"Using manual matching\n"));

			if (s->havefids == 0) {
				s->errv = SI_NO_FIDUCIALS_ERR;
				sprintf(s->errm,"Chart recognition definition file doesn't contain fiducials");
				goto sierr;		/* Error */
			}
			if (compute_man_ptrans(s, sfid))
				goto sierr;

			/* Do the actual scan given out manual transformation matrix */ 
			if (s->verb >= 2)
				DBG((dbgo,"About to setup value scanrdg boxes\n"));
			if (setup_sboxes(s))	
				goto sierr;
			if (s->verb >= 2)
				DBG((dbgo,"About to read raster values\n"));
			if (do_value_scan(s))
				goto sierr;

		} else {				/* Automatic matching */

			/* Attempt to match input file with reference */
			if (s->verb >= 2)
				DBG((dbgo,"About to match features\n"));
			if ((rv = do_match(s)) != 0) {
				if (rv == 1) {	/* No reasonable rotation found */
					s->errv = SI_POOR_MATCH;
					sprintf(s->errm,"Pattern match wasn't good enough");
				}
				goto sierr;
			}

			/* If there is patch matching data and more than one */
			/* feasible matching rotation, try and discriminate between them. */
			if (s->xpt && s->norots > 1) {
				int i, j;
				int flags = s->flags;
			
				s->flags &= ~SI_SHOW_SAMPLED_AREA;	/* Don't show areas for trials */

				/* For each candidate rotation, scan in the pixel values */
				for (s->crot = 0; s->crot < s->norots; s->crot++) {
		
					/* Compute transformation from reference to input file */
					if (s->verb >= 2)
						DBG((dbgo,"About to compute match transform for rotation %f deg.\n",
						          DEG(s->rots[s->crot].irot)));
					if (compute_ptrans(s))
						goto sierr;
			
					/* Setup the input boxes ready for scanning in the input values */
					if (s->verb >= 2)
						DBG((dbgo,"About to setup value scanrdg boxes\n"));
					if (setup_sboxes(s))	
						goto sierr;
			
					/* Scan in the pixel values */
					if (s->verb >= 2)
						DBG((dbgo,"About to read raster values\n"));
					if (do_value_scan(s))
						goto sierr;
		
					/* Copy to this rotation values so that the best can be restored */
					if (s->xpt != 0) {			/* Got expected patch values to compare with */
						if (s->verb >= 2)
							DBG((dbgo,"About to compute expected value correlation\n"));
						if (compute_xcc(s))
							goto sierr;
					}
				}

				/* Pick the best from the candidate rotation */
				if (s->verb >= 2) {
					DBG((dbgo,"Expected value distance values are:\n"));
					for (i = 0; i < s->norots; i++) {
						DBG((dbgo,"%d, rot %f: %f\n", i, DEG(s->rots[i].irot), s->rots[i].xcc));
					}
				}

				for (j = 0, i = 1; i < s->norots; i++) {
					if (s->rots[i].xcc < s->rots[j].xcc)
						j = i;
				}

				if (s->verb >= 2)
					DBG((dbgo,"Chosen rotation %f deg. as best\n",DEG(s->rots[j].irot)));
	
				s->crot = j;
				s->flags = flags;		/* Restore flags */
			}

			/* Setup transformation to be that for chosen rotation for diagnostics */
			if (s->verb >= 2)
				DBG((dbgo,"About to compute final match transform\n"));
			if (compute_ptrans(s))
				goto sierr;

			if (s->verb >= 2)
				DBG((dbgo,"Improve match\n"));
			if (improve_match(s))
				goto sierr;

			/* After choosing rotation of improving the fit, rescan the values */
			if (s->verb >= 2)
				DBG((dbgo,"About to setup value scanrdg boxes\n"));
			if (setup_sboxes(s))	
				goto sierr;
			if (s->verb >= 2)
				DBG((dbgo,"About to read raster values\n"));
			if (do_value_scan(s))
				goto sierr;
		}

		if (s->flags & SI_SHOW_SBOX) {
			show_sbox(s);		/* Draw sample box outlines on diagnostic raster */
		}
	}
	if (s->flags & SI_SHOW_LINES)
		if(show_lines(s))
			goto sierr;		/* Error */

sierr:;
	if (s->verb >= 2)
		DBG((dbgo,"About to write diag file\n"));
	if (scanrd_write_diag(s))
		return (scanrd *)s;	/* Error */

	return (scanrd *)s;
}


/********************************************************************************/

/* Allocate the basic scanrd object */
/* Return NULL on failure to allocate */
/* Need to check errv for other problems */
static scanrd_
*new_scanrd(
	int flags,			/* option flags */
	int verb,			/* verbosity level */
	double gammav,		/* Approximate gamma encoding of image (0.0 = default 2.2) */
	int (*write_line)(void *ddata, int y, char *src),	/* Write RGB line of diag file */
	void *ddata,		/* Opaque data for write_line() */
	int w, int h, 		/* Width and Height of input raster in pixels */
	int d, int td, int p,	/* Useful plane Depth, Total depth, Bit presision of input pixels */
	int (*read_line)(void *fdata, int y, char *dst),	/* Read RGB line of source file */
	void *fdata,		/* Opaque data for read_line() */

	char *refname		/* reference file name */
) {
	scanrd_ *s;

	if ((s = (scanrd_ *)calloc(1, sizeof(scanrd_))) == NULL)
		return NULL;

	/* Public functions */
	s->public.reset = scanrd_reset;
	s->public.read = scanrd_read;
	s->public.error = scanrd_error;
	s->public.free = free_scanrd;

	if (flags & (SI_SHOW_ROT | SI_SHOW_PERS | SI_SHOW_IMPL | SI_SHOW_ALL_LINES))
		flags |= SI_SHOW_LINES;		/* Key all line stuff off SI_SHOW_LINES */

	if (flags & (SI_SHOW_SBOX_OUTLINES | SI_SHOW_SBOX_NAMES | SI_SHOW_SBOX_AREAS))
		flags |= SI_SHOW_SBOX;;		/* Key all sample box stuff off SI_SHOW_SBOX */

	if (write_line == NULL)
		flags &= ~SI_SHOW_FLAGS;	/* If no diag file, turn off show flags */

	s->flags = flags;
	s->verb = verb;

	s->errv = 0;
	s->errm[0] = '\0';

	if (gammav <= 0.0)
		gammav = 2.2;			/* default */
	s->gammav = gammav;
	s->width = w;
	s->height = h;
	s->depth = d;
	s->tdepth = td;
	s->bpp = p;

	if (d > MXDE)  {
		s->errv = SI_PIX_DEPTH_ERR;
		sprintf(s->errm,"scanrd: Pixel depth is too large");
		return s;
	}

	if (p != 8 && p != 16) {
		s->errv = SI_BIT_DEPTH_ERR;
		sprintf(s->errm,"scanrd: Pixel bits/pixel is not 8 or 16");
		return s;
	}
	if (p == 8)
		s->bypp = 1;
	else
		s->bypp = 2;

	if (verb >= 2)
		DBG((dbgo,"Verbosity = %d, flags = 0x%x\n",verb, flags));

	/* RGB Diagnostic output raster array requested */
	if ((flags & SI_SHOW_FLAGS) && write_line != NULL) {
		if ((s->out = malloc(3 * w * h)) == NULL) {
			s->errv = SI_MALLOC_DIAG_RAST;
			sprintf(s->errm,"scanrd: Diagnostic output raster array malloc failed");
			return s;
		}
	}
	
	s->noslines = 0;
	s->novlines = 0;
	s->gdone = NULL;
	s->irot = 0.0;
	s->norots = 0;
	
	s->ppc[0] = 0.0;
	s->ppc[1] = 0.0;
	s->ppc[2] = 0.0;
	s->ppc[3] = 0.0;

	/* Set overall perspective transform to null */
	s->ptrans[0] = 1.0;
	s->ptrans[1] = 0.0;
	s->ptrans[2] = 0.0;
	s->ptrans[3] = 0.0;
	s->ptrans[4] = 1.0;
	s->ptrans[5] = 0.0;
	s->ptrans[6] = 0.0;
	s->ptrans[7] = 0.0;

	INIT_ELIST(s->xelist);
	INIT_ELIST(s->yelist);
	INIT_ELIST(s->ixelist);
	INIT_ELIST(s->iyelist);
	INIT_ELIST(s->rxelist);
	INIT_ELIST(s->ryelist);
	s->rbox_shrink = 0.9;
	s->xpt = 0;
	
	s->nsbox = 0;
	s->sboxes = NULL;
	s->sbstart = NULL;
	s->sbend = NULL;
	s->csi = 0;
	s->cei = 0;
	s->alist = NULL;
	
	s->next_read = 0;

	s->refname = refname;

	s->inited = 0;
	s->vrego = s->vregn = NULL;
	s->no_vo = s->no_vn = 0;
	s->hrego = s->hregn = NULL;
	s->no_ho = s->no_hn = 0;
	s->th = TH;
	s->divval = 0.25;
	s->adivval = 0.0;
	s->divc = 0;

	/* aa line init */
	s->aa_inited = 0;	/* Let line init do the rest */
	s->coverage = NULL;

	/* Callbacks */
	s->read_line = read_line;
	s->fdata = fdata;

	s->write_line = write_line;
	s->ddata = ddata;

	return s;
}

static void free_elist_array(elist *el);

/* Free the object up */
static void
free_scanrd(
scanrd *ps
) {
	scanrd_ *s = (scanrd_ *)ps;	/* Cast public to private */
	points *tp;

	free_elist_array(&s->xelist);
	free_elist_array(&s->yelist);
	free_elist_array(&s->ixelist);
	free_elist_array(&s->iyelist);
	free_elist_array(&s->rxelist);
	free_elist_array(&s->ryelist);
	
	if (s->sboxes != NULL)
		free(s->sboxes);
	if (s->sbstart != NULL)
		free(s->sbstart);
	if (s->sbend != NULL)
		free(s->sbend);
	s->alist = NULL;
	
	/* Free up done line list */
	tp = s->gdone;
	FOR_ALL_ITEMS(points, tp)
		if (tp->r != NULL)
			free(tp->r);
		free(tp);
	END_FOR_ALL_ITEMS(tp);
	s->gdone = NULL;
	
	/* Points were deleted with gdone ??? */
	if(s->vrego != NULL)
		free(s->vrego);
	if(s->vregn)
		free(s->vregn);
	if(s->hrego != NULL)
		free(s->hrego);
	if(s->hregn != NULL)
		free(s->hregn);
	s->inited = 1;

	/* Free up output diag array */
	if (s->out != NULL)
		free(s->out);
	
	/* Free up aa line array */
	if (s->coverage != NULL)
		free(s->coverage);
	free(s);
}


/* Return the error flag, and set the message pointer */
static unsigned int
scanrd_error(scanrd *ps, char **errm) {
	scanrd_ *s = (scanrd_ *)ps;	/* Cast public to private */
	*errm = s->errm;
	return s->errv;
}

/********************************************************************************/
static int analize(scanrd_ *s, unsigned char *inp[6], int y);

/* Read in and process the input file */
/* Return non-zero on error */
static int
read_input(scanrd_ *s) {
	unsigned char *in[6];		/* Pointer to six input buffers */
	int w = s->width;			/* Raster width */
	int h = s->height;			/* Raster height */
	int i, y;

	/* Allocate input line buffers */
	for (i = 0; i < 6; i++) {
		if ((in[i] = malloc(s->tdepth * w * s->bypp)) == NULL) {
			s->errv = SI_MALLOC_INPUT_BUF;
			sprintf(s->errm,"scanrd: Failed to malloc input line buffers");
			return 1;
		}
	}

	/* Prime the input buffers with 5 lines */
	for (y = 0; y < 5; y++) {
		if (s->read_line(s->fdata, y, (char *)in[y])) {
			s->errv = SI_RAST_READ_ERR;
			sprintf(s->errm,"scanrd: read_line() returned error");
			return 1;
		}
	}
	/* Process the tiff file line by line (Assume at least 6 lines in total raster) */
	for (; y < h; ++y) {
		unsigned char *tt;
		if (s->read_line(s->fdata, y, (char *)in[5])) {
			s->errv = SI_RAST_READ_ERR;
			sprintf(s->errm,"scanrd: read_line() returned error");
			return 1;
		}

		if (analize(s, in, y)) {
			return 1;
		}

		tt = in[0];		/* Shuffle buffers about */
		in[0] = in[1];
		in[1] = in[2];
		in[2] = in[3];
		in[3] = in[4];
		in[4] = in[5];
		in[5] = tt;

	}
	s->adivval /= (double)s->divc;	/* Average divider value, 1.0 = 0 degrees, 0.0 = 45 degrees */
	if (s->adivval < 0.0)
		s->adivval = 0.0;
	else if (s->adivval > 1.0)
		s->adivval = 1.0;

	if (s->verb >= 2)
		DBG((dbgo,"adivval = %f\n",s->adivval));

	/* Free the input line buffers */
	for (i = 0; i < 6; i++)
		free(in[i]);

	return 0;
}

/********************************************************************************/

#ifdef NEVER	/* Before 22/5/2004 */
#define	THRN 1.0			/* Threshold above average ratio - numerator */
#define	THRD 2.0			/* Threshold above average ratio - denominator */

#define THAWF 1.0 			/* Threshold average adaptation filter weight, fixed value (TH) */
#define THAWP 4.0 			/* Threshold average adaptation filter weight, previous value */
#define THAWN 1.0 			/* Threshold average adaptation filter weight, new value */

#else			/* Current values */

#define	THRN 1.0			/* Threshold above average ratio - numerator */
#define	THRD 1.5			/* Threshold above average ratio - denominator */

#define THAWF 1.0 			/* Threshold average adaptation filter weight, fixed value (TH) */
#define THAWP 5.0 			/* Threshold average adaptation filter weight, previous value */
#define THAWN 1.0 			/* Threshold average adaptation filter weight, new value */

#endif

/* ~~~ minimum raster size needs to be specified/checked ~~~~ */
#define MIN_NO_LINES    16		/* Minimum number of valid fitted lines to estimate rotation */

/* Criteria for accepting lines for angle calculation (valid lines) */
#define MAX_MWID_TO_LEN    0.1
#define MIN_POINT_TO_AREA  0.9	/* Minimum point desity over the lines area */
#define SD_WINDOW          1.5	/* Allow += 1.5 of a standard deviation for robust angle calc. */
#define ELISTCDIST 800		/* 1/ELISTCDIST = portion of refence edge list legth to coalesce over */

/* Criteria for accepting lines for improring final fit */
#define IMP_MATCH 0.10			/* Proportion of average tick spacing */

/* The following should be scaled to the resolution of the image ? */
#define MIN_POINTS 10		/* Minimum points to calculate line */
#define MIN_LINE_LENGTH    10.0
#define CUT_CHUNKS 128		/* cut groups along diagonals - must be power of 2 */

static int add_region(scanrd_ *s, region *rego, int no_o, region *regn, int no_n, int y);

/* Process a line of the TIFF file */
/* return non-zero on error */
static int
analize(
scanrd_ *s,
unsigned char *inp[6],		/* current and previous 5 lines */
int y						/* Current line y */
) {
	int w = s->width;
	int stride = s->tdepth * s->width;	/* In pixels */
	unsigned short *gamma = s->gamma;
	int x,i;
	unsigned short *inp2[6];	/* current and previous 5 lines (16bpp) equivalent of inp[] */
	unsigned char  *in[6];		/* six input lines (8bpp) */
	unsigned short *in2[6];		/* six input lines (16bpp) */
	region *tr;
	double tdh,tdv;				/* Horizontal/virtical detect levels */
	double tdmag;
	double atdmag = 0.0;		/* Average magnitude over a line */
	int atdmagc = 0;			/* Average magnitude over a line count */
	double linedv = 0.0;		/* Lines average divider value */
	int linedc = 0;				/* Lines average count */
	int xo3 = s->tdepth * 3;	/* Xoffset by 3 pixels */
	int xo2 = s->tdepth * 2;	/* Xoffset by 2 pixels */
	int xo1 = s->tdepth * 1;	/* Xoffset by 1 pixels */

	for (x = 0; x < 6; x++)		/* Create 16 bpp version of line pointers */
		inp2[x] = (unsigned short *)inp[x];

	if (s->inited == 0) {
		/* Init gamma conversion lookup and region tracking. */
		/* The assumption is that a typical chart has an approx. visually */
		/* uniform distribution of samples, so that a typically gamma */
		/* encoded scan image will have an average pixel value of 50%. */
		/* If a the chart has a different gamma encoding (ie. linear), */
		/* then we convert it to gamma 2.2 encoded to (hopefuly) enhance */
		/* the patch contrast. */
		if (s->bpp == 8)
		    for (i = 0; i < 256; i++) {
				int byteb1;
			
				byteb1 = (int)(0.5 + 255 * pow( i / 255.0, s->gammav/2.2 ));
				gamma[i] = byteb1;
			}
		else
		    for (i = 0; i < 65536; i++) {
				int byteb1;
			
				byteb1 = (int)(0.5 + 65535 * pow( i / 65535.0, s->gammav/2.2 ));
				gamma[i] = byteb1;
			}

		if ((s->vrego = (region *) malloc(sizeof(region) * (w+1)/2)) == NULL) {
			s->errv = SI_MALLOC_VREGION;
			sprintf(s->errm,"vreg malloc failed");
			return 1;
		}
		s->no_vo = 0;
		if ((s->vregn = (region *) malloc(sizeof(region) * (w+1)/2)) == NULL) {
			s->errv = SI_MALLOC_VREGION;
			sprintf(s->errm,"vreg malloc failed");
			return 1;
		}
		s->no_vn = 0;
		if ((s->hrego = (region *) malloc(sizeof(region) * (w+1)/2)) == NULL) {
			s->errv = SI_MALLOC_VREGION;
			sprintf(s->errm,"vreg malloc failed");
			return 1;
		}
		s->no_ho = 0;
		if ((s->hregn = (region *) malloc(sizeof(region) * (w+1)/2)) == NULL) {
			s->errv = SI_MALLOC_VREGION;
			sprintf(s->errm,"vreg malloc failed");
			return 1;
		}
		s->no_hn = 0;
		INIT_LIST(s->gdone);
		s->inited = 1;
	}

	/* Un-gamma correct the latest input line */
	if (s->bpp == 8)
		for (x = 0; x < stride; x++)
			inp[5][x] = (unsigned char)gamma[inp[5][x]];
	else
		for (x = 0; x < stride; x++)
			inp2[5][x] = gamma[inp2[5][x]];

	/* Compute difference output for line y-3 */
	atdmagc = w - 5;		/* Magnitude count (to compute average) */
	for (x = 3; x < (w-2); x++) {		/* Allow for -3 to +2 from x */
		unsigned char *out = s->out;
		int e;
		int ss;
		int idx = ((y-2) * w + x) * 3;		/* Output raster index in bytes */

		if (s->bpp == 8)
			for (i = 0; i < 6; i++)
				in[i] = inp[i] + x * s->tdepth;	/* Strength reduce */
		else
			for (i = 0; i < 6; i++) {
				in2[i] = inp2[i] + x * s->tdepth;	/* Strength reduce */
				in[i] = (unsigned char *)in2[i];	/* track 8bpp pointers */
			}

		if (s->flags & SI_SHOW_IMAGE) {		/* Create B&W image */
			toRGB(out + idx, in[2], s->depth, s->bpp);		/* Convert to RGB */
			out[idx] = out[idx+1] = out[idx+2] = (2 * out[idx] + 7 * out[idx+1] + out[idx+2])/10;
		}

		ss = 0;		/* Sign of cross components the same vote */
		tdh = tdv = 0.0;
		
		if (s->bpp == 8)
			for (e = 0; e < s->depth; e++) {
				int d1,d2;
				/* Compute Gxp */
				d1 = -in[0][-xo3+e] + -in[0][-xo2+e] + -in[0][-xo1+e]
				                              + -in[0][ 0+e] + -in[0][ xo1+e] + -in[0][ xo2+e]
				   + -in[1][-xo3+e] + -in[1][-xo2+e] + -in[1][-xo1+e]
				                              + -in[1][ 0+e] + -in[1][ xo1+e] + -in[1][ xo2+e] 
				   + -in[2][-xo3+e] + -in[2][-xo2+e] + -in[2][-xo1+e]
				                              + -in[2][ 0+e] + -in[2][ xo1+e] + -in[2][ xo2+e]
				   +  in[3][-xo3+e] +  in[3][-xo2+e] +  in[3][-xo1+e]
				                              +  in[3][ 0+e] +  in[3][ xo1+e] +  in[3][ xo2+e]
				   +  in[4][-xo3+e] +  in[4][-xo2+e] +  in[4][-xo1+e]
				                              +  in[4][ 0+e] +  in[4][ xo1+e] +  in[4][ xo2+e]
				   +  in[5][-xo3+e] +  in[5][-xo2+e] +  in[5][-xo1+e]
				                              +  in[5][ 0+e] +  in[5][ xo1+e] +  in[5][ xo2+e];
				/* Compute Gyp */
				d2 = -in[0][-xo3+e] + -in[1][-xo3+e] + -in[2][-xo3+e]
				                              + -in[3][-xo3+e] + -in[4][-xo3+e] + -in[5][-xo3+e]
				   + -in[0][-xo2+e] + -in[1][-xo2+e] + -in[2][-xo2+e]
				                              + -in[3][-xo2+e] + -in[4][-xo2+e] + -in[5][-xo2+e]
				   + -in[0][-xo1+e] + -in[1][-xo1+e] + -in[2][-xo1+e]
				                              + -in[3][-xo1+e] + -in[4][-xo1+e] + -in[5][-xo1+e]
				   +  in[0][   0+e] +  in[1][   0+e] +  in[2][   0+e]
				                              +  in[3][   0+e] +  in[4][   0+e] +  in[5][   0+e]
				   +  in[0][+xo1+e] +  in[1][+xo1+e] +  in[2][+xo1+e]
				                              +  in[3][+xo1+e] +  in[4][+xo1+e] +  in[5][+xo1+e]
				   +  in[0][+xo2+e] +  in[1][+xo2+e] +  in[2][+xo2+e]
				                              +  in[3][+xo2+e] +  in[4][+xo2+e] +  in[5][+xo2+e];

				if ((d1 >= 0 && d2 >=0)
				 || (d1 < 0 && d2 < 0))
					ss++;				/* Sign was the same */
				tdh += d1/4.5 * d1/4.5;		/* (4.5 = 6x6/4x2, to scale original tuned values) */
				tdv += d2/4.5 * d2/4.5;
			}
		else
			for (e = 0; e < s->depth; e++) {
				int d1,d2;
				/* Compute Gxp */
				d1 = -in2[0][-xo3+e] + -in2[0][-xo2+e] + -in2[0][-xo1+e]
				                              + -in2[0][ 0+e] + -in2[0][ xo1+e] + -in2[0][ xo2+e]
				   + -in2[1][-xo3+e] + -in2[1][-xo2+e] + -in2[1][-xo1+e]
				                              + -in2[1][ 0+e] + -in2[1][ xo1+e] + -in2[1][ xo2+e] 
				   + -in2[2][-xo3+e] + -in2[2][-xo2+e] + -in2[2][-xo1+e]
				                              + -in2[2][ 0+e] + -in2[2][ xo1+e] + -in2[2][ xo2+e]
				   +  in2[3][-xo3+e] +  in2[3][-xo2+e] +  in2[3][-xo1+e]
				                              +  in2[3][ 0+e] +  in2[3][ xo1+e] +  in2[3][ xo2+e]
				   +  in2[4][-xo3+e] +  in2[4][-xo2+e] +  in2[4][-xo1+e]
				                              +  in2[4][ 0+e] +  in2[4][ xo1+e] +  in2[4][ xo2+e]
				   +  in2[5][-xo3+e] +  in2[5][-xo2+e] +  in2[5][-xo1+e]
				                              +  in2[5][ 0+e] +  in2[5][ xo1+e] +  in2[5][ xo2+e];
				/* Compute Gyp */
				d2 = -in2[0][-xo3+e] + -in2[1][-xo3+e] + -in2[2][-xo3+e]
				                              + -in2[3][-xo3+e] + -in2[4][-xo3+e] + -in2[5][-xo3+e]
				   + -in2[0][-xo2+e] + -in2[1][-xo2+e] + -in2[2][-xo2+e]
				                              + -in2[3][-xo2+e] + -in2[4][-xo2+e] + -in2[5][-xo2+e]
				   + -in2[0][-xo1+e] + -in2[1][-xo1+e] + -in2[2][-xo1+e]
				                              + -in2[3][-xo1+e] + -in2[4][-xo1+e] + -in2[5][-xo1+e]
				   +  in2[0][   0+e] +  in2[1][   0+e] +  in2[2][   0+e]
				                              +  in2[3][   0+e] +  in2[4][   0+e] +  in2[5][   0+e]
				   +  in2[0][+xo1+e] +  in2[1][+xo1+e] +  in2[2][+xo1+e]
				                              +  in2[3][+xo1+e] +  in2[4][+xo1+e] +  in2[5][+xo1+e]
				   +  in2[0][+xo2+e] +  in2[1][+xo2+e] +  in2[2][+xo2+e]
				                              +  in2[3][+xo2+e] +  in2[4][+xo2+e] +  in2[5][+xo2+e];

				if ((d1 >= 0 && d2 >=0)
				 || (d1 < 0 && d2 < 0))
					ss++;				/* Sign was the same */
				
				tdh += d1/(4.5 * 257) * d1/(4.5 * 257);		/* Scale to 0..255 range */
				tdv += d2/(4.5 * 257) * d2/(4.5 * 257);
			}

		tdmag = tdh + tdv;

		if (tdmag < (32.0 * s->th))
			atdmag += tdmag;	/* Average magnitude over a line */
		else
			atdmag += 32.0 * s->th;

		/* if over threshold */
		/* (Cut long lines up to prevent long lines being */
		/* (thrown away due to attached blobs) */
		if (tdmag >= s->th
		 && (x & (CUT_CHUNKS-1)) != (y & (CUT_CHUNKS-1))) {
			double tt;
			double av;		/* Angle value of current pixel */
			tt = (tdv - tdh)/(tdh + tdv);	/* Partial angle */
			linedv += fabs(tt);
			linedc++;

			if (ss >= (s->depth/2+1))	/* Assume signs are the same if clear majority */
				av = 3.0 + tt;
			else
				av = 1.0 - tt;

			/* Separate the orthogonal elements */
			if (av  >= s->divval && av < (s->divval + 2.0)) {
				if (s->flags & SI_SHOW_DIFFSH)
					out[idx] = (char)255;		/* Red */
				/* Add point to new region */
				/* See if we can add to last region */
				if (s->no_hn > 0 && x == s->hregn[s->no_hn-1].hx)
					s->hregn[s->no_hn-1].hx++;
				else {	/* Add another */
					if (s->no_hn >= (w+1)/2) {
						s->errv = SI_INTERNAL;
						sprintf(s->errm,"Internal, no_hn is too large");
						return 1;
					}
					s->hregn[s->no_hn].lx = x;
					s->hregn[s->no_hn].hx = x+1;
					s->hregn[s->no_hn].p = NULL;
					s->no_hn++;
				}
			} else {
				if (s->flags & SI_SHOW_DIFFSV)
					out[idx+1] = (char)255;		/* Green */
				/* Add point to new region */
				/* See if we can add to last region */
				if (s->no_vn > 0 && x == s->vregn[s->no_vn-1].hx)
					s->vregn[s->no_vn-1].hx++;
				else {	/* Add another */
					if (s->no_vn >= (w+1)/2) {
						s->errv = SI_INTERNAL;
						sprintf(s->errm,"Internal, no_vn is too large");
						return 1;
					}
					s->vregn[s->no_vn].lx = x;
					s->vregn[s->no_vn].hx = x+1;
					s->vregn[s->no_vn].p = NULL;
					s->no_vn++;
				}
			}
		}
	}

	if (linedc != 0) {						/* Adapt divider value to line */
		linedv /= (double)linedc;			/* Compute average over the line */
		linedv = (linedv * linedv);			/* Square to even out linedv vs angle */
		linedv = (1.65 * (linedv - 0.12));	/* Compensate for random offsets */
		s->adivval += linedv;
		s->divc++;
		s->divval = (7.0 * s->divval + linedv)/8.0;		/* Average over 8 lines */
		if (s->divval < 0.0)
			s->divval = 0.0;
		else if (s->divval > 1.0)
			s->divval = 1.0;
		if (s->verb >= 5)
			DBG((dbgo,"linedv = %f, divval = %f\n",linedv,s->divval));
	}

	/* Adjust the threshold */
	atdmag /= (double)atdmagc;				/* compute average magnitude over the line */
	s->th = (s->th * THRD)/(THRN + s->divval);/* Convert threshold to average */
	s->th = ((THAWF * TH) + (THAWP * s->th) + (THAWN * atdmag))/(THAWF + THAWP + THAWN);
	s->th = (s->th * (THRN + s->divval))/THRD;	/* Convert average back to threshold */

	/* Add vertical regions */
	if (add_region(s,s->vrego,s->no_vo,s->vregn,s->no_vn,y-2))
		return 1;

	/* Add horizontal regions */
 	if (add_region(s,s->hrego,s->no_ho,s->hregn,s->no_hn,y-2))
		return 1;

	/* shuffle them along */
	tr = s->vrego;
	s->vrego = s->vregn;	/* move new to old */
	s->vregn = tr;			/* old to new */
	s->no_vo = s->no_vn;
	s->no_vn = 0;

	tr = s->hrego;
	s->hrego = s->hregn;	/* move new to old */
	s->hregn = tr;			/* old to new */
	s->no_ho = s->no_hn;
	s->no_hn = 0;

	return 0;
}

/********************************************************************************/
/* Point list code */

/* allocate a new (empty) points structure */
/* return NULL on error */
static points *
new_points(
scanrd_ *s
) {
	points *ps;
	static int pn = 0;
	if ((ps = (points *) malloc(sizeof(points))) == NULL) {
		s->errv = SI_MALLOC_POINTS;
		sprintf(s->errm,"new_points: malloc failed");
		return NULL;
	}
	ps->mxno = 0;
	ps->no = 0;
	ps->nop = 0;
	ps->r = NULL;
	ps->pn = pn;
	pn++;
	return ps;
}

/* destroy a points structure */
static void
destroy_points(
scanrd_ *s,
points *ps) {
	if (ps->r != NULL)	/* Free any array pointed to */
		free(ps->r);
	free (ps);
}

/* Add another run to a points object */
/* return non-zero on error */
static int
add_run(
scanrd_ *s,
points *ps,
int lx,
int hx,
int y)
	{
	if (ps->no == ps->mxno) {	/* Need some more space */
		ps->mxno = (2 * ps->mxno) + 5;		/* New size */
		if ((ps->r = (run *) realloc(ps->r, sizeof(run) * ps->mxno)) == NULL) {
			s->errv = SI_REALLOC_POINTS;
			sprintf(s->errm,"add_run: realloc failed");
			return 1;
		}
	}
	ps->r[ps->no].lx = lx;
	ps->r[ps->no].hx = hx;
	ps->r[ps->no].y = y;	
	ps->no++;					/* One more run */
	ps->nop += hx - lx;			/* Total of pixels */
	return 0;
}

/* copy src points to dest */
/* Return non-zero on error */
static int
copy_points(
scanrd_ *s,
points *dst,
points *src
) {
	int i;
	for (i = 0; i < src->no; i++) {
		if (add_run(s,dst,src->r[i].lx,src->r[i].hx,src->r[i].y))
			return 1;
	}
	return 0;
}

/********************************************************************************/

/* Add a new region of points to the line points lists */
/* Note that regions are assumed to be non-overlapping x sorted */
/* Return non-zero on error */
static int
add_region(
scanrd_ *s,
region *rego,		/* Old regions */
int no_o,			/* No of old region */
region *regn,		/* New regions */
int no_n,			/* No of new region */
int y				/* Y value */
) {
	int osp,op,np;	/* Old/new pointers */

	osp = 0;
	for (np = 0; np < no_n; np++) {	/* Process all new runs */
		/* Advance start pointer until we get to runs that may touch */
#ifdef DIAGN
		while (osp < no_o && rego[osp].hx < regn[np].lx)
#else
		while (osp < no_o && rego[osp].hx <= regn[np].lx)
#endif
			osp++;
		/* For all old runs that may touch new */
#ifdef DIAGN
		for(op = osp; op < no_o && rego[op].lx <= regn[np].hx; op++) {
#else
		for(op = osp; op < no_o && rego[op].lx < regn[np].hx; op++) {
#endif

#ifdef DIAGN
			if (rego[op].hx >= regn[np].lx  && rego[op].lx <= regn[np].hx) {
#else
			if (rego[op].hx > regn[np].lx  && rego[op].lx < regn[np].hx) {
#endif
				/* Old region touches new */
				if (regn[np].p == NULL) {		/* No group for new yet */
					regn[np].p = rego[op].p;	/* Make part of the same group */
					if (add_run(s, regn[np].p,regn[np].lx,regn[np].hx,y)) /* add new run to group */
						return 1;
				} else if (regn[np].p != rego[op].p) {	/* Touches different group */
					int j;
					points *tp = rego[op].p;			/* Old region to be renamed/merged */
					if (copy_points(s,regn[np].p,tp))	/* Merge old with current new */
						return 1;	/* Error */
					DEL_LINK(s->gdone,tp);				/* Don't need other any more */
					for (j = 0; j < no_o; j++)			/* Fix all references to this group */
						if (rego[j].p == tp)
							rego[j].p = regn[np].p;
					for (j = 0; j < no_n; j++)
						if (regn[j].p == tp)
							regn[j].p = regn[np].p;
					destroy_points(s,tp);
				}
			}
		}
		/* Finished all relevant old runs */
		if (regn[np].p == NULL) {	/* No old touched, so start new group */
			if ((regn[np].p = new_points(s)) == NULL)
				return 1;		/* Error */
			ADD_ITEM_TO_TOP(s->gdone,regn[np].p);	/* Stash it in points list */
			if (add_run(s, regn[np].p,regn[np].lx,regn[np].hx,y))	/* add new run to group */
				return 1;		/* Error */
		}
	}
	return 0;
}

/********************************************************************************/

/* Apply partial perspective to an xy point */
/* (We omit the two offset parameters, since we don't need them) */
void ppersp(scanrd_ *s, double *xx, double *yy, double x, double y, double *ppc) {
	double den;

	/* Offset the partial perspective transform */
	x -= ppc[2];
	y -= ppc[3];

	den = ppc[0] * x + ppc[1] * y + 1.0;

	if (fabs(den) < 1e-6) {
		if (den < 0.0)
			den = -1e-6;
		else
			den = 1e-6;
	}
	*xx = x/den + ppc[2];
	*yy = y/den + ppc[3];
}


/* Apply inverse partial perspective to an xy point */
void invppersp(scanrd_ *s, double *x, double *y, double xx, double yy, double *ppc) {
	double den;

	/* Offset the partial perspective transform */
	xx -= ppc[2];
	yy -= ppc[3];

	den = - ppc[0] * xx - ppc[1] * yy + 1.0;

	if (fabs(den) < 1e-6) {
		if (den < 0.0)
			den = -1e-6;
		else
			den = 1e-6;
	}
	*x = xx/den + ppc[2];
	*y = yy/den + ppc[3];
}

/********************************************************************************/

/* Compute the least squares best line fit for a group */
/* Return non-zero if failed */
static int
points_to_line(
scanrd_ *s,
points *ps) {
	int i,j;
	point *vv;			/* Point vectors */
	int nop = ps->nop;	/* Number of points */
	double sx,sy;		/* Sum */
	double mx,my;		/* Mean */
	double a;			/* Angle, Clockwise from 12o'clock */
	double mw,len;		/* mean width, length */
	double x1,y1,x2,y2;	/* Start/end point of fitted line */

	ps->flag = 0;

	if (nop < MIN_POINTS)			/* Don't bother if too few pixels */
		return 0;

	/* Convert runs to individual points, and compute mean */
	if ((vv = (point *) malloc(sizeof(point) * nop)) == NULL) {
		s->errv = SI_MALLOC_POINT2LINE;
		sprintf(s->errm,"scanrd: points_to_line: malloc failed");
		return 1;
	}

	sx = sy = 0.0;
	for (j = i = 0; i < ps->no; i++) {	/* For all runs */
		int x,y;
		int hx = ps->r[i].hx, lx = ps->r[i].lx;

		y = ps->r[i].y;
		sy += (hx - lx) * y;
		for (x = lx; x < hx; x++, j++) {	/* Convert to points */
			sx += x;
			vv[j].x = x;
			vv[j].y = y;
		}
	}
	mx = sx/(double)nop;	/* Centroid (mean) of points */
	my = sy/(double)nop;

	/* Offset points to centroid */
	for (i=0; i < nop; i++) {
		vv[i].x -= mx;
		vv[i].y -= my;
	}
	
	/* Compute ad and bd, then A, B, C */
	/* From Graphics Gems V, pp 91-97, */
	/* "The Best Least-Squares Line Fit" */
	/* by David Alciatore and Rick Miranda. */
	{
		double ad, bd;		/* a' and b' values */
		double xd, yd;		/* temp x' and y' */
		double A, B;		/* line equation */
		double abn;			/* A & B normalizer */

		xd = yd = bd = 0.0;
		for (i = 0; i < nop; i++) {
			double x, y;

			x = vv[i].x;
			y = vv[i].y;
			xd += x * x;
			yd += y * y;
			bd += x * y;
		}
		ad = xd - yd;

		/* Equation of best fit line is Ax + By = C */
		A = 2 * bd;
		B = -(ad + sqrt(ad * ad + 4.0 * bd * bd));
		/* C = A * mx + B * my; */

		/* Compute angle */
		/* A = abn * cos(a), B = -abn * sin(a) */

		abn = sqrt(A * A + B * B);	/* Normalize A & B */
		if (fabs(abn) < 1e-6) {		/* No dominant direction */
			a = 0.0;
		} else {
			a = acos(A/abn);
		}
		/* Make angle +ve */
		while (a < 0.0) a += M_PI;
	}

	/* Now figure out the bounding box for the line + other stats */
	{
		double s,c;
		double pl,nl;		/* Positive length, negative length */
		s = sin(a);
		c = cos(a);
		for (mw = 0.0, pl = 0.0, nl = 0.0, i = 0; i < nop; i++)
			{
			double npj;		/* Projection onto normal */
			double lpj;		/* Projection onto line */
			npj = -c * vv[i].x + s * vv[i].y;
			if (npj < 0)
				mw -= npj;
			else
				mw += npj;
			lpj = s * vv[i].x + c * vv[i].y;
			if (lpj > pl)
				pl = lpj;
			if (lpj < nl)
				nl = lpj;
			}
		mw = 2.0 * mw/(double)nop;		/* Mean width */
	
		x1 = mx + s * nl;
		y1 = my + c * nl;
		x2 = mx + s * pl;
		y2 = my + c * pl;
		len = pl - nl;
	}

	ps->mx = mx;		/* Mean point */
	ps->my = my;
	ps->a = a;			/* Angle */
	ps->mw = mw;		/* Mean width */
	ps->len = len;		/* Mean length */
	ps->x1 = x1;		/* Start/end point of fitted line */
	ps->y1 = y1;
	ps->x2 = x2;
	ps->y2 = y2;
	ps->flag = F_LINESTATS;	/* Line stats valid */

	/* Compute the Constrained to 90 degrees angle */
	/* We use the adivval to figure out where to split angles */
	/* Split at 0 if adivval == 0.0, split at 45 if adivval == 1.0 */
	if (a >= (M_PI * (1.0 - s->adivval/4.0)))
		ps->ca = a - M_PI;
	else if (a >= (M_PI * (0.5 - s->adivval/4.0)))
		ps->ca = a - M_PI_2;
	else
		ps->ca = a;

	if (s->verb >= 5)
	 	DBG((dbgo,"Angle %f, CA = %f, length = %f, mean width  = %f, Line %f,%f to %f,%f\n",
			DEG(a),DEG(ps->ca),len,mw,x1,y1,x2,y2));
	free(vv);

/* printf("~~stats: mw = %f, len = %f, mw/len = %f, area = %f\n",
	mw, len, mw/len, ((double)nop/(len * (mw + 0.01)))); */
	/* Look at stats to see what lines are acceptable for further processing */
	if ( len >= MIN_LINE_LENGTH
	  && mw/len <= MAX_MWID_TO_LEN
	  && ((double)nop/(len * (mw + 0.01))) >= MIN_POINT_TO_AREA) {
		ps->flag |= F_VALID;	/* Line stats valid to use */
/* printf("~~set valid\n"); */
	}
	return 0;
}

static int
calc_lines(
scanrd_ *s
) {
	points *tp;
	s->noslines = 0;
	s->novlines = 0;
	tp = s->gdone;
	FOR_ALL_ITEMS(points, tp)
		if (points_to_line(s,tp))
			return 1;				/* Error */
		if (tp->flag & F_LINESTATS)	/* Line stats valid */
			s->noslines++;
		if (tp->flag & F_VALID)		/* Valid for angle calcs */
			s->novlines++;

		/* Save orininal raster (non partial perspective corrected) values */
		if (tp->flag & F_VALID) {
			tp->pmx = tp->mx;
			tp->pmy = tp->my;
			tp->px1 = tp->x1;
			tp->py1 = tp->y1;
			tp->px2 = tp->x2;
			tp->py2 = tp->y2;
		}
	END_FOR_ALL_ITEMS(tp);
	return 0;
}

static int show_line(scanrd_ *s, int x1, int y1, int x2, int y2, unsigned long c);

/* Show the edge detected lines */
static int
show_lines(
scanrd_ *s
) {
	points *tp;
	int outw = s->width;
	int outh = s->height;
	/* For SI_SHOW_ROT */
	double cirot,sirot;		/* cos and sin of -irot */
	cirot = cos(-s->irot);
	sirot = sin(-s->irot);

	tp = s->gdone;
	FOR_ALL_ITEMS(points, tp)
		if ((s->flags & SI_SHOW_ALL_LINES) || (tp->flag & F_VALID))
			{
			unsigned long col = 0xffffff;		/* default color is white */
			double x1 = tp->px1, y1 = tp->py1, x2 = tp->px2, y2 = tp->py2;
			/* For SI_SHOW_ROT */

			/* Show partial perspective corrected lines */
			if (s->flags & (SI_SHOW_ROT | SI_SHOW_PERS)) {
				invppersp(s, &x1, &y1, x1, y1, s->ppc);
				invppersp(s, &x2, &y2, x2, y2, s->ppc);
				col = 0xffff00;	/* cyan */
			}

			/* Show rotation correction of lines + color coding yellow and red */
			if (s->flags & SI_SHOW_ROT) {
				double tx1, ty1, tx2, ty2;
				double a = tp->a - s->irot;

				tx1 = x1;
				ty1 = y1;
				tx2 = x2;
				ty2 = y2;

				/* Rotate about center of raster */
				x1 = (tx1-outw/2.0) * cirot + (ty1-outh/2.0) * sirot;
				y1 = -(tx1-outw/2.0) * sirot + (ty1-outh/2.0) * cirot;
				x2 = (tx2-outw/2.0) * cirot + (ty2-outh/2.0) * sirot;
				y2 = -(tx2-outw/2.0) * sirot + (ty2-outh/2.0) * cirot;

				x1 += outw/2.0;		/* Rotate about center of raster */
				y1 += outh/2.0;
				x2 += outw/2.0;
				y2 += outh/2.0;
				if ((a >= -0.08 && a <= 0.08) || (a >= (M_PI-0.08) && a <= (M_PI+0.08))
				 || (a >= (M_PI_2-0.08) && a <= (M_PI_2+0.08)))
					col = 0x00ffff;	/* yellow */
				else
					col = 0x0000ff;	/* Red */
			}
			/* Show just lines used for fit improvement in blue */
			if (s->flags & SI_SHOW_IMPL) {
				if (tp->flag & F_IMPROVE)
					col = 0xff4040;	/* blue */
			}
			show_line(s,(int)(x1+0.5),(int)(y1+0.5),(int)(x2+0.5),(int)(y2+0.5),col);
		}
	END_FOR_ALL_ITEMS(tp);
	return 0;
}


/********************************************************************************/

/* Definition of the optimization function handed to powell() */
static double
pfunc(void *ss, double p[]) {
	scanrd_ *s = (scanrd_ *)ss;
	points *tp;
	double aa;		/* Average angle */
	double va, rva;	/* Variance */
	double wt;		/* Total weighting = sum of line lengths */
	double pw;
	double dw;		/* Discrimination width */
	
//printf("~1 %f %f %f %f %f %f\n", p[0],p[1],p[2],p[3],p[4],p[5]);

	/* Correct the perspective of all the edge lines using the parameters */
	/* and compute the mean angle */
	aa = 0.0;		/* Average constrained angle */
	wt = 0.0;		/* Total weighting = sum of line lengths */
	tp = s->gdone;
	FOR_ALL_ITEMS(points, tp)
		if (tp->flag & F_LONGENOUGH) {
			double a, ca;
			invppersp(s, &tp->x1, &tp->y1, tp->px1, tp->py1, p);
			invppersp(s, &tp->x2, &tp->y2, tp->px2, tp->py2, p);

			/* Compute the angle */
			a = atan2(tp->x2 - tp->x1,tp->y2 - tp->y1);

			/* Make angle +ve */
			while (a < 0.0)
				a += M_PI;

			/* Compute the Constrained to 90 degrees angle */
			/* We use the adivval to figure out where to split angles */
			/* Split at 0 if adivval == 0.0, split at 45 if adivval == 1.0 */
			if (a >= (M_PI * (1.0 - s->adivval/4.0)))
				ca = a - M_PI;
			else if (a >= (M_PI * (0.5 - s->adivval/4.0)))
				ca = a - M_PI_2;
			else
				ca = a;

			tp->a = a;
			tp->ca = ca;

			aa += tp->len * ca;
			wt += tp->len;
		}
	END_FOR_ALL_ITEMS(tp);
	aa /= wt;

	/* Calculate the angle variance */
	va = 0.0;
	tp = s->gdone;
	wt = 0.0;
	FOR_ALL_ITEMS(points, tp)
		if (tp->flag & F_LONGENOUGH) {
			double tt;
			tt = tp->ca - aa;
			va += tp->len * tt * tt;
			wt += tp->len;
		}
	END_FOR_ALL_ITEMS(tp);
	va = va/wt;

	/* Calculate the a robust angle variance */
	rva = 0.0;
	wt = 0.0;
	dw = sqrt(va) * 3.1;		/* Allow += 0.5 of a standard deviation */
	if (dw < 0.0001)			/* A perfect chart may have dw of zero */
		dw = 0.0001;
	tp = s->gdone;
	FOR_ALL_ITEMS(points, tp)
		if (tp->flag & F_LONGENOUGH && fabs(tp->ca - aa) <= dw) {
			double tt;
			tt = tp->ca - aa;
			rva += tp->len * tt * tt;
			wt += tp->len;
		}
	END_FOR_ALL_ITEMS(tp);
	if (wt > 0.0) {
		rva = rva/wt;
		va = rva;
	}

	/* Add some regularization to stop it going crazy */
	pw = 0.0;
	pw += 0.01 * (fabs(p[0]) + fabs(p[1]));
	pw += 0.0001 * (fabs(p[2]/s->width - 0.5) + fabs(p[3]/s->height - 0.5));
	va += pw;

	return va;
}

/* Calculate the partial perspective correction factors */
/* Return non-zero if failed */
static int
calc_perspective(
scanrd_ *s
) {
	points *tp;
	int nl;			/* Number of lines used */
	double ml;		/* Minimum length */
	double pc[4];	/* Perspective factors */
	double ss[4];	/* Initial search distance */
	double rv;		/* Return value */
	int rc = 0;		/* Return code */

	if (s->novlines < MIN_NO_LINES) {
		s->errv = SI_FIND_PERSPECTIVE_FAILED;
		sprintf(s->errm,"Not enough valid lines to compute perspective");
		return 1;
	}

	/* Find the longest line */
	ml = 0.0;
	tp = s->gdone;
	FOR_ALL_ITEMS(points, tp)
		if (tp->flag & F_VALID) {
			if (tp->len > ml)
				ml = tp->len;
		}
	END_FOR_ALL_ITEMS(tp);
	
	/* Make minimum line length to be included in angle */
	/* calculation 1% of longest line */
	ml *= 0.01;

	/* Mark lines long enough to participate in angle calculation */
	tp = s->gdone;
	FOR_ALL_ITEMS(points, tp)
		if (tp->flag & F_VALID && tp->len >= ml)
			tp->flag |= F_LONGENOUGH;
	END_FOR_ALL_ITEMS(tp);

	/* Locate the perspective correction factors that minimze the */
	/* variance of the mean angle. */

	pc[0] = 0.0;
	pc[1] = 0.0;
	pc[2] = 0.5 * s->width;
	pc[3] = 0.5 * s->height;

	ss[0] = 0.0001;
	ss[1] = 0.0001;
	ss[2] = 1.0001;
	ss[3] = 1.0001;
	rc = powell(&rv, 4, pc,ss,1e-8,2000,pfunc,s, NULL, NULL);

	if (rc == 0) {
		points *tp;
	
		DBG((dbgo,"Perspective correction factors = %f %f %f %f\n",
		       pc[0],pc[1],pc[2],pc[3]));
		
		s->ppc[0] = pc[0];
		s->ppc[1] = pc[1];
		s->ppc[2] = pc[2];
		s->ppc[3] = pc[3];

		/* Implement the perspective correction */
		tp = s->gdone;
		FOR_ALL_ITEMS(points, tp)
			if (tp->flag & F_LONGENOUGH) {
				double a, ca;
				invppersp(s, &tp->x1, &tp->y1, tp->px1, tp->py1, s->ppc);
				invppersp(s, &tp->x2, &tp->y2, tp->px2, tp->py2, s->ppc);
				tp->mx = 0.5 * (tp->x2 + tp->x1);
				tp->my = 0.5 * (tp->y2 + tp->y1);
				tp->len = sqrt((tp->x2 - tp->x1) * (tp->x2 - tp->x1)
				             + (tp->y2 - tp->y1) * (tp->y2 - tp->y1));
	
				/* Compute the angle */
				a = atan2(tp->x2 - tp->x1,tp->y2 - tp->y1);
	
				/* Make angle +ve */
				while (a < 0.0)
					a += M_PI;
	
				/* Compute the Constrained to 90 degrees angle */
				/* We use the adivval to figure out where to split angles */
				/* Split at 0 if adivval == 0.0, split at 45 if adivval == 1.0 */
				if (a >= (M_PI * (1.0 - s->adivval/4.0)))
					ca = a - M_PI;
				else if (a >= (M_PI * (0.5 - s->adivval/4.0)))
					ca = a - M_PI_2;
				else
					ca = a;
	
				tp->a = a;
				tp->ca = ca;
			}
		END_FOR_ALL_ITEMS(tp);
	}

	return 0;
}

/********************************************************************************/
/* Calculate the image rotation */
/* Return non-zero if failed */
static int
calc_rotation(
scanrd_ *s
) {
	points *tp;
	int nl;			/* Number of lines used */
	double ml;		/* Minimum length */
	double aa;		/* Average angle */
	double sd,dw;	/* Standard deviation, deviation window */
	double wt;		/* Total weighting = sum of line lengths */

	if (s->novlines < MIN_NO_LINES) {
		s->errv = SI_FIND_ROTATION_FAILED;
		sprintf(s->errm,"Not enough valid lines to compute rotation angle");
		return 1;
	}

	/* Find the longest line */
	tp = s->gdone;
	ml = 0.0;
	FOR_ALL_ITEMS(points, tp)
		if (tp->flag & F_VALID) {
			if (tp->len > ml)
				ml = tp->len;
		}
	END_FOR_ALL_ITEMS(tp);
	
	/* Make minimum line length to be included in angle */
	/* calculation 1% of longest line */
	ml *= 0.01;

	/* Calculate the mean angle */
	aa = 0.0;
	wt = 0.0;		/* Total weighting = sum of line lengths */
	tp = s->gdone;
	FOR_ALL_ITEMS(points, tp)
		if (tp->flag & F_VALID && tp->len >= ml) {
			aa += tp->len * tp->ca;
			wt += tp->len;
		}
	END_FOR_ALL_ITEMS(tp);
	aa /= wt;

	if (s->verb >= 2)
		DBG((dbgo,"Mean angle = %f\n",DEG(aa)));

	/* Calculate the angle standard deviation */
	tp = s->gdone;
	sd = 0.0;
	FOR_ALL_ITEMS(points, tp)
		if (tp->flag & F_VALID && tp->len >= ml) {
			double tt;
			tt = tp->ca - aa;
			sd += tp->len * tt * tt;
		}
	END_FOR_ALL_ITEMS(tp);

	sd = sqrt(sd/wt);

	if (s->verb >= 2)
		DBG((dbgo,"Standard deviation = %f\n",DEG(sd)));

	/* Now re-compute the angle while rejecting any that fall outside one standard deviation */
	s->irot = 0.0;
	wt = 0.0;					/* Total weighting = sum of line lengths */
	nl = 0;
	dw = sd * SD_WINDOW;		/* Allow += 0.5 of a standard deviation */
	if (dw < 0.01)				/* A perfect chart may have dw of zero */
		dw = 0.01;
	tp = s->gdone;
	FOR_ALL_ITEMS(points, tp)
		if (tp->flag & F_VALID && tp->len >= ml && fabs(tp->ca - aa) <= dw) {
			s->irot += tp->len * tp->ca;
			wt += tp->len;
			nl++;
		}
	END_FOR_ALL_ITEMS(tp);
	if (nl < (MIN_NO_LINES/2)) {
		s->errv = SI_FIND_ROTATION_FAILED;
		sprintf(s->errm,"%d consistent lines is not enough to compute rotation angle",nl);
		return 1;
	}
	s->irot /= wt;

	if (s->verb >= 2)
		DBG((dbgo,"Robust mean angle = %f from %d lines\n",DEG(s->irot),nl));

	return 0;
}

/********************************************************************************/
/* Coalesce close entries of an edge list */
/* return non-zero on error */
static int
coalesce_elist(
scanrd_ *s,
elist *el,
int close			/* Closeness factor, smaller = coarser */
) {
	double r;		/* Margin for coalescence */
	int i,k;

	if (el->c < 2)		/* Need at least 2 entries */
		return 0;

	r = (el->a[el->c-1].pos - el->a[0].pos)/(double)close;
	for (k = 0, i = 1; i < el->c; i++) {
		if ((el->a[i].pos - el->a[k].pos) <= r) {
			/* Merge the two */
			double lk = el->a[k].len;
			double li = el->a[i].len;
			el->a[k].pos = (el->a[k].pos * lk + el->a[i].pos * li)/(lk + li);
			el->a[k].len = lk + li;
			if (el->a[k].p1 > el->a[i].p1)		/* Track overall start/end points */
				el->a[k].p1 = el->a[i].p1;
			if (el->a[k].p2 < el->a[i].p2)
				el->a[k].p2 = el->a[i].p2;
			continue;
		}
		k++;		/* Inc destination pointer */
		if (k != i)
			el->a[k] = el->a[i];	/* shuffle data down */
	}
	k++;		/* one past last out entry */
	el->c = k;
	return 0;
}

static int invert_elist(scanrd_ *s, elist *dl, elist *sl);
static void debug_elist(scanrd_ *s, elist *el);

/* Make up the x and y edge lists */
/* Return non-zero if failed */
static int
calc_elists(
scanrd_ *s,
int ref			/* 1 if generating reference lists */
) {
	int outw = s->width;
	int outh = s->height;
	points *tp;
	int i,j;
	double cirot,sirot;		/* cos and sin of -irot */
	elist xl, yl;	/* Temporary X and Y edge lists array */
	elist tl;		/* temporary crossing list */

	/* Allocate structures for edge lists */
	if ((xl.a = (epoint *) malloc(sizeof(epoint) * s->novlines)) == NULL) {
		s->errv = SI_MALLOC_ELIST;
		sprintf(s->errm,"scanrd: calc_elist: malloc failed - novlines = %d",s->novlines);
		return 1;
	}
	xl.c = 0;
	if ((yl.a = (epoint *) malloc(sizeof(epoint) * s->novlines)) == NULL) {
		s->errv = SI_MALLOC_ELIST;
		sprintf(s->errm,"scanrd: calc_elist: malloc failed - novlines = %d",s->novlines);
		return 1;
	}
	yl.c = 0;

	/* Put valid lines into one of the two edge list arrays */
	cirot = cos(-s->irot);
	sirot = sin(-s->irot);
	tp = s->gdone;
	FOR_ALL_ITEMS(points, tp)
		if (tp->flag & F_VALID) {
			/* Rotate the point about 0,0 by angle -irot */
			double x,y,a;
			double mx = tp->mx, my = tp->my;
			
			if (ref) {	/* Rotate about center of raster for reference generation */
				mx -= outw/2.0;		/* Rotate about center of raster */
				my -= outh/2.0;
				x = mx * cirot + my * sirot + outw/2.0;
				y = -mx * sirot + my * cirot + outh/2.0;
			} else {	/* Rotate about 0,0 for matching */
				x = mx * cirot + my * sirot;
				y = -mx * sirot + my * cirot;
			}
			a = tp->a - s->irot;
			if ((a >= -0.08 && a <= 0.08) || (a >= (M_PI-0.08) && a <= (M_PI+0.08))) {
				xl.a[xl.c].pos = x;
				xl.a[xl.c].len = tp->len;
				xl.a[xl.c].p1 = y - tp->len/2.0;
				xl.a[xl.c].p2 = y + tp->len/2.0;
				xl.c++;
			} else if (a >= (M_PI_2-0.08) && a <= (M_PI_2+0.08)) {
				yl.a[yl.c].pos = y;
				yl.a[yl.c].len = tp->len;
				yl.a[yl.c].p1 = x - tp->len/2.0;
				yl.a[yl.c].p2 = x + tp->len/2.0;
				yl.c++;
			}
		}
	END_FOR_ALL_ITEMS(tp);

	/* ~~~~ need to check that lists have a reasonable number of entries ~~~~~ */

	/* now sort the lists */
#define HEAP_COMPARE(A,B)  (A.pos < B.pos)
	HEAPSORT(epoint,xl.a,xl.c);
	HEAPSORT(epoint,yl.a,yl.c);
#undef HEAP_COMPARE
	
	/* Copy the temporary lists to the real lists */
	if ((s->xelist.a = (epoint *) malloc(sizeof(epoint) * xl.c)) == NULL) {
		s->errv = SI_MALLOC_ELIST;
		sprintf(s->errm,"scanrd: calc_elist: malloc failed, xl.c = %d",xl.c);
		return 1;
	}
	s->xelist.c = xl.c;
	for (i=0; i < xl.c; i++)
		s->xelist.a[i] = xl.a[i];
	if ((s->yelist.a = (epoint *) malloc(sizeof(epoint) * yl.c)) == NULL) {
		s->errv = SI_MALLOC_ELIST;
		sprintf(s->errm,"scanrd: calc_elist: malloc failed, yl.c = %d",yl.c);
		return 1;
	}
	s->yelist.c = yl.c;
	for (i=0; i < yl.c; i++)
		s->yelist.a[i] = yl.a[i];

	/* Coalese close entries of the final lists */
	if (coalesce_elist(s, &s->xelist,ELISTCDIST))
		return 1;
	if (coalesce_elist(s, &s->yelist,ELISTCDIST))
		return 1;

	/* Calculate crossing count for lines in the X and y lists */
	if ((tl.a = (epoint *) malloc(sizeof(epoint) * (xl.c > yl.c ? xl.c : yl.c))) == NULL) {
		s->errv = SI_MALLOC_ELIST;
		sprintf(s->errm,"scanrd: calc_elist: malloc failed, xl.c = %d, yl.c = %d",xl.c,yl.c);
		return 1;
	}
	/* X list */
	for (i = 0; i < s->xelist.c; i++) {
		double ppos = s->xelist.a[i].pos;
		double pp,np;		/* Previous and next pos */
		if ((i-1) >= 0)
			pp = (ppos + s->xelist.a[i-1].pos)/2.0;	/* Half distance to next line */
		else
			pp = -1e6;
		if ((i+1) < s->xelist.c)
			np = (ppos + s->xelist.a[i+1].pos)/2.0;	/* Half distance to next line */
		else
			np = 1e6;

		/* For all the lines in the Y list */
		for (tl.c = j = 0; j < yl.c; j++) {
			double pos = yl.a[j].pos;
			double p1 = yl.a[j].p1;
			double p2 = yl.a[j].p2;
			if (p1 <= pp)
				p1 = pp;
			if (p2 >= np)
				p2 = np;
			/* If crosses on this lines X within +-0.5 of line each side */
			if (p1 <= np && p2 >= pp) {
				tl.a[tl.c].pos = pos;
				tl.a[tl.c].len = p2 - p1;
				tl.a[tl.c].p1 = p1;
				tl.a[tl.c].p2 = p2;
				tl.c++;
			}
		}
		/* now coalesce the crossings */
		if (coalesce_elist(s,&tl,200))
			return 1;
		/* Put count in line we're working on */
		s->xelist.a[i].ccount = (double)tl.c;
		pp = ppos;
	}

	/* Y list */
	for (i = 0; i < s->yelist.c; i++) {
		double ppos = s->yelist.a[i].pos;
		double pp,np;		/* Previous and next pos */
		if ((i-1) >= 0)
			pp = (ppos + s->yelist.a[i-1].pos)/2.0;	/* Half distance to next line */
		else
			pp = -1e6;
		if ((i+1) < s->xelist.c)
			np = (ppos + s->yelist.a[i+1].pos)/2.0;	/* Half distance to next line */
		else
			np = 1e6;

		for (tl.c = j = 0; j < xl.c; j++) {
			double pos = xl.a[j].pos;
			double p1 = xl.a[j].p1;
			double p2 = xl.a[j].p2;
			if (p1 <= pp)
				p1 = pp;
			if (p2 >= np)
				p2 = np;
			/* If crosses on this lines Y within +-0.5 of line each side */
			if (p1 <= np && p2 >= pp) {
				tl.a[tl.c].pos = pos;
				tl.a[tl.c].len = p2 - p1;
				tl.a[tl.c].p1 = p1;
				tl.a[tl.c].p2 = p2;
				tl.c++;
			}
		}
		/* now coalesce the crossings */
		if (coalesce_elist(s,&tl,200))
			return 1;
		/* Put count in line we're working on */
		s->yelist.a[i].ccount = (double)tl.c;
		pp = ppos;
	}

	/* Normalize the length and ccount */
		{
		double tlen;	/* Total length maximum */
		double tcmax;	/* Total count maximum */
		for (tlen = tcmax = 0.0, i=0; i < s->xelist.c; i++) {
			if (tlen < s->xelist.a[i].len)
				tlen = s->xelist.a[i].len;
			if (tcmax < s->xelist.a[i].ccount)
				tcmax = s->xelist.a[i].ccount;
		}
		for (i=0; i < s->xelist.c; i++) {
			s->xelist.a[i].len /= tlen;
			s->xelist.a[i].ccount /= tcmax;
		}
		for (tlen = tcmax = 0.0, i=0; i < s->yelist.c; i++) {
			if (tlen < s->yelist.a[i].len)
				tlen = s->yelist.a[i].len;
			if (tcmax < s->yelist.a[i].ccount)
				tcmax = s->yelist.a[i].ccount;
		}
		for (i=0; i < s->yelist.c; i++) {
			s->yelist.a[i].len /= tlen;
			s->yelist.a[i].ccount /= tcmax;
		}
	}

	/* Create the inverted lists for any rotation matching */
	if (invert_elist(s, &s->ixelist, &s->xelist))
		return 1;
	if (invert_elist(s, &s->iyelist, &s->yelist))
		return 1;

	if (s->verb >= 3) {
		DBG((dbgo,"\nxelist:\n"));
		debug_elist(s,&s->xelist);
		DBG((dbgo,"\nixelist:\n"));
		debug_elist(s,&s->ixelist);
		DBG((dbgo,"\nyelist:\n"));
		debug_elist(s,&s->yelist);
		DBG((dbgo,"\niyelist:\n"));
		debug_elist(s,&s->iyelist);
	}

	/* Clean up */
	free(xl.a);
	free(yl.a);
	free(tl.a);
	return 0;
}

/********************************************************************************/
/* Write the elists out to a file */

/* Increment a string counter */
static void
strinc(
char *s
) {
	int i,n,c;	/* Length of string and carry flag */
	n = strlen(s);
	for (c = 1, i = n-1; i >= 0 && c != 0; i--) {
		char sval = ' ';
		if (s[i] == '9') {
			s[i] = '0';
			sval = '1';
			c = 1;
		} else if (s[i] == 'z') {
			s[i] = 'a';
			sval = 'a';
			c = 1;
		} else if (s[i] == 'Z') {
			s[i] = 'A';
			sval = 'A';
			c = 1;
		} else {
			s[i]++;
			c = 0;
		}
		if (i == 0 && c != 0) {
			/* Assume there is some more space */
			for (i = n; i >= 0; i--)
				s[i+1] = s[i];
			s[0] = sval;
			break;
		}
	}
}

/* Write out the match reference information */
/* Return non-zero on error */
static int
write_elists(
scanrd_ *s
) {
	char *fname = s->refname;			/* Path of file to write to */
	FILE *elf;
	int i;

	if ((elf=fopen(fname,"w"))==NULL) {
		s->errv = SI_REF_WRITE_ERR;
		sprintf(s->errm,"write_elists: error opening match reference file '%s'",fname);
		return 1;
	}

	fprintf(elf,"REF_ROTATION %f\n\n",DEG(s->irot));

	fprintf(elf,"XLIST %d\n",s->xelist.c);
	for (i = 0; i < s->xelist.c; i++)
		fprintf(elf,"  %f %f %f\n",s->xelist.a[i].pos, s->xelist.a[i].len, s->xelist.a[i].ccount);
	fprintf(elf,"\n");

	fprintf(elf,"YLIST %d\n",s->yelist.c);
	for (i = 0; i < s->yelist.c; i++)
		fprintf(elf,"  %f %f %f\n",s->yelist.a[i].pos, s->yelist.a[i].len, s->yelist.a[i].ccount);
	fprintf(elf,"\n");

	if ((fclose(elf)) == EOF) {
		s->errv = SI_REF_WRITE_ERR;
		error("write_elists: Unable to close match reference file '%s'\n",fname);
		return 1;
	}
	return 0;
}

/* Read in an elist reference file */
/* return non-zero on error */
/* (~~~ the line counting is rather broken ~~~) */
static int
read_relists(
scanrd_ *s
) {
	char *fname = s->refname;			/* Path of file to read from */
	FILE *elf;
	int i,l = 1;
	int rv;
	char *em;	/* Read error message */

	if ((elf=fopen(fname,"r"))==NULL) {
		s->errv = SI_REF_READ_ERR;
		sprintf(s->errm,"read_elists: error opening match reference file '%s'",fname);
		return 1;
	}

	s->fid[0] = s->fid[1] = 0.0;
	s->fid[2] = s->fid[3] = 0.0;
	s->fid[4] = s->fid[5] = 0.0;
	s->fid[6] = s->fid[7] = 0.0;

	/* BOXES */
	for(;;) {
		if((rv = fscanf(elf,"BOXES %d",&s->nsbox)) == 1) {
			l++;
			break;
		}
		if (rv == EOF) {
			em = "Didn't find BOXES before end of file";
			goto read_error;
		}
		if (rv == 0) {
			while ((rv = getc(elf)) != '\n' && rv != EOF);
			l++;
		}
	}

	/* Allocate structures for boxes */
	if ((s->sboxes = (sbox *) calloc(s->nsbox, sizeof(sbox))) == NULL) {
		s->errv = SI_MALLOC_REFREAD;
		sprintf(s->errm,"read_elist, malloc failed");
		return 1;
	}
	for (i = 0; i < s->nsbox;) {
		char xfix1[20], xfix2[20], yfix1[20],yfix2[20];
		char xfirst[20];
		double ox,oy,w,h,xi,yi;
		char xf[20];
		double x;

		if(fscanf(elf," %19s %19s %19s %19s %19s %lf %lf %lf %lf %lf %lf",xfirst ,xfix1, xfix2, yfix1, yfix2, &w, &h, &ox, &oy, &xi, &yi) != 11) {
			em = "Read of BOX failed";
			goto read_error;
		}
		l++;

		/* If Fiducial. Typically top left, top right, botton right, bottom left. */
		if (xfirst[0] == 'F') {
			s->fid[0] = atof(yfix1);
			s->fid[1] = atof(yfix2);
			s->fid[2] = w;
			s->fid[3] = h;
			s->fid[4] = ox;
			s->fid[5] = oy;
			s->fid[6] = xi;
			s->fid[7] = yi;
			s->fidsize = fabs(s->fid[2] - s->fid[0]) + fabs(s->fid[5] - s->fid[3]);
			s->fidsize /= 80.0;
			s->havefids = 1;

//printf("~1 fiducials %f %f, %f %f %f, %f\n",w, h, ox,oy, xi, yi);
			continue;
		}
		for(;;) {		/* Do Y increment */
			x = ox;
			strcpy(xf,xfix1);
			for(;;) {	/* Do X increment */
				if (i >= s->nsbox) {
					em = "More BOXes that declared";
					goto read_error;
				}
				/* '_' is used as a null string marker for single character single cells */
				if (xf[0] == '_')
					sprintf(s->sboxes[i].name,"%s",yfix1);
				else if (yfix1[0] == '_')
					sprintf(s->sboxes[i].name,"%s",xf);
				else {	/* Y indicates Y name comes first */
					if (xfirst[0] == 'Y')
						sprintf(s->sboxes[i].name,"%s%s",yfix1,xf);
					else	/* X or D */
						sprintf(s->sboxes[i].name,"%s%s",xf,yfix1);
				}
				if (xfirst[0] == 'D')
					s->sboxes[i].diag = 1;	/* Diagnostic box - don't print name or read pixels */
				else
					s->sboxes[i].diag = 0;
				s->sboxes[i].x1 = x;
				s->sboxes[i].y1 = oy;
				s->sboxes[i].x2 = x + w;
				s->sboxes[i].y2 = oy + h;

				/* Misc. init. of new sbox */
				s->sboxes[i].xpt[0] = -1.0;		/* No default expected value */
				
				i++;
				x += xi;
				if (strcmp(xf,xfix2) == 0)
					break;
				strinc(xf);
				}
			if (strcmp(yfix1,yfix2) == 0)
				break;
			oy += yi;
			strinc(yfix1);
		}
	}

	/* BOX_SHRINK */
	for(;;) {
		if((rv = fscanf(elf,"BOX_SHRINK %lf ",&s->rbox_shrink)) == 1) {
			l++;
			break;
		}
		if (rv == EOF) {
			em = "Didn't find BOX_SHRINK before end of file";
			goto read_error;
		}
		if (rv == 0) {
			while ((rv = getc(elf)) != '\n' && rv != EOF); 
			l++;
		}
	}

	/* XLIST */
	for(;;) {
		if((rv = fscanf(elf,"XLIST %d ",&s->rxelist.c)) == 1) {
			l++;
			break;
		}
		if (rv == EOF) {
			em = "Didn't find XLIST before end of file";
			goto read_error;
		}
		if (rv == 0) {
			while ((rv = getc(elf)) != '\n' && rv != EOF);
			l++;
		}
	}
	/* Allocate structures for ref edge lists */
	if ((s->rxelist.a = (epoint *) malloc(sizeof(epoint) * s->rxelist.c)) == NULL) {
		s->errv = SI_MALLOC_REFREAD;
		sprintf(s->errm,"read_elist, malloc failed");
		return 1;
	}
	for (i = 0; i < s->rxelist.c; i++) {
		if (fscanf(elf," %lf %lf %lf ",
		    &s->rxelist.a[i].pos, &s->rxelist.a[i].len, &s->rxelist.a[i].ccount) != 3) {
			em = "Failed to read an XLIST line";
			goto read_error;
		}
		l++;
	}

	/* YLIST */
	for(;;) {
		if ((rv = fscanf(elf,"YLIST %d ",&s->ryelist.c)) == 1) {
			l++;
			break;
		}
		if (rv == EOF) {
			em = "Didn't find YLIST before end of file";
			goto read_error;
		}
		if (rv == 0) {
			while ((rv = getc(elf)) != '\n' && rv != EOF);
			l++;
		}
	}
	if ((s->ryelist.a = (epoint *) malloc(sizeof(epoint) * s->ryelist.c)) == NULL) {
		s->errv = SI_MALLOC_REFREAD;
		sprintf(s->errm,"read_elist, malloc failed");
		return 1;
	}
	for (i = 0; i < s->ryelist.c; i++) {
		if (fscanf(elf," %lf %lf %lf ",
		    &s->ryelist.a[i].pos, &s->ryelist.a[i].len, &s->ryelist.a[i].ccount) != 3)
			{
			em = "Failed to read an YLIST line";
			goto read_error;
		}
		l++;
	}

	/* EXPECTED */
	{
		int j;
		int isxyz = 0;
		int nxpt = 0;
		char csps[20];

		for(;;) {
			if ((rv = fscanf(elf,"EXPECTED %19s %d ",csps, &nxpt)) == 2) {
				l++;
				if (strcmp(csps, "XYZ") == 0) {
					isxyz = 1;
					break;
				} else if (strcmp(csps, "LAB") == 0) {
					isxyz = 0;
					break;
				}
			}
			if (rv == EOF) {
				break;
			}
			if (rv == 0) {
				while ((rv = getc(elf)) != '\n' && rv != EOF);
				l++;
			}
		}
		for (j = 0; j < nxpt; j++) {
			char name[20];
			double val[3];
			if (fscanf(elf," %19s %lf %lf %lf ",
			    name, &val[0], &val[1], &val[2]) != 4)
				{
				em = "Failed to read an EXPECTED line";
				goto read_error;
			}
			l++;
			/* Now locate the matching box */
			for (i = 0; i < s->nsbox; i++) {
				if (strcmp(s->sboxes[i].name, name) == 0) {	/* Found it */
					if (isxyz) {
						XYZ2Lab(s->sboxes[i].xpt, val);
					} else {
						s->sboxes[i].xpt[0] = val[0];
						s->sboxes[i].xpt[1] = val[1];
						s->sboxes[i].xpt[2] = val[2];
					}
					s->xpt = 1;
					break;
				}
			}
			if (i >= s->nsbox) {
				em = "Failed to locate matching sample box in EXPECTED list";
				goto read_error;
			}
		}
	}

	if ((fclose(elf)) == EOF) {
		s->errv = SI_REF_WRITE_ERR;
		error("read_elists: Unable to close match reference file '%s'\n",fname);
		return 1;
	}

	/* Generate length normalization factor */
	{
		double tlen;	/* Total of normalized length */
		for (tlen = 0.0, i=0; i < s->rxelist.c; i++)
			tlen += s->rxelist.a[i].len;
		s->rxelist.lennorm = tlen;
		for (tlen = 0.0, i=0; i < s->ryelist.c; i++)
			tlen += s->ryelist.a[i].len;
		s->ryelist.lennorm = tlen;
	}

	if (s->verb >= 3) {
		DBG((dbgo,"\nrxelist:\n"));
		debug_elist(s, &s->rxelist);
		DBG((dbgo,"\nryelist:\n"));
		debug_elist(s, &s->ryelist);
	}

	return 0;

read_error:;
	s->errv = SI_REF_FORMAT_ERR;
	sprintf(s->errm,"read_relist failed at line %d in file %s: %s\n",l,fname,em);
	return 1;
}

/********************************************************************************/
/* Create an inverted direction elist */
/* return non-zero on error */
static int
invert_elist(
scanrd_ *s,
elist *dl,		/* Destination list */
elist *sl		/* Source list */
) {
	int i,j, rc = sl->c;
	
	*dl = *sl;	/* Copy all the structure elements */

	/* Allocate space in the destination list */
	if ((dl->a = (epoint *) malloc(sizeof(epoint) * rc)) == NULL) {
		s->errv = SI_MALLOC_ELIST;
		sprintf(s->errm,"invert_elist: malloc failed");
		return 1;
	}

	/* Copy the array data and reverse its order */
	for (i = 0, j = rc-1; i < rc; i++,j--) {
		dl->a[j]  = sl->a[i];		/* Copy array element */
		dl->a[j].pos = -dl->a[j].pos;	/* Invert position */
	}
	return 0;
}

/* Print out elist */
static void
debug_elist(
scanrd_ *s,
elist *el
) {
	int i, rc = el->c;
	
	DBG((dbgo,"Elist has %d entries allocated at 0x%x\n",el->c,(unsigned long)el->a));
	DBG((dbgo,"lennorm = %f\n",el->lennorm));
	for (i = 0; i < rc; i++)
		DBG((dbgo,"  [%d] = %f %f %f\n",i,el->a[i].pos,el->a[i].len,el->a[i].ccount));
}

/* Free the array data in an elist */
static void
free_elist_array(elist *el) {
	free(el->a);
	el->c = 0;
}

/********************************************************************************/
/* !!!!!!! */
/* NEED TO RESOLVE WHY current code is better in some cases, but */
/* not in others. */

#ifndef NEVER	/* Current code */

/* Compute a correlation between two elists */
static double
elist_correl(
scanrd_ *s,
elist *r,		/* Reference list */
elist *t,		/* Target list */
double off, double scale,		/* Offset and scale of target to ref */
int verb		/* Verbose mode */
) {
	int i, j, rc = r->c;
	double cc = 0.0;		/* Correlation */
	double marg = (r->a[rc-1].pos - r->a[0].pos)/150.0;	/* determines sharpness of pos. match */
	double marg2 = marg * 3.0;		/* Don't contribute anything outside this distance */
	
	for (i = j = 0; i < t->c; i++) {
		int ri;		/* Reference index */
		double dd,d1,d2;		/* Distance to nearest reference */
		double pos = (t->a[i].pos + off) * scale;
		double len = t->a[i].len;
		double cnt = t->a[i].ccount;
		while (pos > r->a[j+1].pos && j < (r->c-2)) j++;
		d1 = fabs(pos - r->a[j].pos);
		d2 = fabs(r->a[j+1].pos - pos);
		if (d1 < d2) {
			dd = d1;
			ri = j;
		} else {
			dd = d2;
			ri = j+1;
		}
		if (dd <= marg2) {	/* If close enough to reference */
			double ccf, rcnt = r->a[ri].ccount;
			double llf, rlen = r->a[ri].len;
			double df = marg/(marg + dd);
			df *= df;
			ccf = 1.0 - (rcnt > cnt ? rcnt-cnt : cnt-rcnt);
			llf = 1.0 - (rlen > len ? rlen-len : len-rlen);
			/* The weighting gives slightly more emphasis on matching long lines */
			cc += (1.0 + rlen) * (df * llf * ccf);
			if (verb) {
				DBG((dbgo,"---- t[%d] %f %f %f    this cc = %f, running total cc = %f\n     r[%d] %f %f %f, df = %f, llf = %f, ccf = %f\n",
				i,pos,len,cnt,df * llf * ccf,cc,j,r->a[ri].pos,r->a[ri].len,rcnt,df, llf, ccf));
			}
		}
	}
	return cc/(r->lennorm + (double)r->c);		/* Normalize */
}

#else	/* New test code */

/* Compute a correlation between two elists */
static double
elist_correl(
scanrd_ *s,
elist *r,		/* Reference list */
elist *t,		/* Target list */
double off, double scale,		/* Offset and scale of target to ref */
int verb		/* Verbose mode */
) {
	int i, rc = r->c;
	double cc = 0.0;		/* Correlation */
	double marg = (r->a[rc-1].pos - r->a[0].pos)/100.0;	/* determines sharpness of pos. match */
	double marg2 = marg * marg;		/* marg squared */
	
//printf("~1 doing elist_correl\n");
	/* For each reference edge */
	for (i = 0; i < rc; i++) {
		int j[3], jj, bj, tc = t->c;
		double dd, pos, bdd;

		/* Find the closest target edge using binary search. */
		for(bdd = 1e6, j[2] = tc-1, j[0] = 0; j[2] > (j[0]+1);) {
			double dist;
			j[1] = (j[2] + j[0])/2;			/* Trial point */
			dist = r->a[i].pos - (t->a[j[1]].pos + off) * scale;
			
//printf("~1 j1 = %d, j1 = %d, j0 = %d, dist = %f\n",j[2], j[1], j[0], dist);
			if (dist > 0) {
				j[0] = j[1];
			} else {
				j[2] = j[1];
			}
		}

		/* Locate best out of 3 remaining points */
		for (jj = 0; jj < 3; jj++) {
			double dist;
			pos = (t->a[j[jj]].pos + off) * scale;
			dist = r->a[i].pos - pos;
			dd = dist * dist;				/* Distance squared */
			if (dd < bdd) {	/* New closest */
				bdd = dd;
				bj = j[jj];
			}
		}

//printf("~1 best j = %d, bdd = %f, marg2 = %f\n",bj,bdd,marg2);
		/* Compute correlation */
		if (bdd < marg2) {		/* Within our margine */
			double df = (marg2 - bdd)/marg2;		/* Distance factor */
			double llf, rlen = r->a[i].len, len = t->a[i].len;
			double ccf, rcnt = r->a[i].ccount, cnt = t->a[i].ccount;
			double tcc;
			llf = 1.0 - (rlen > len ? rlen-len : len-rlen);
			ccf = 1.0 - (rcnt > cnt ? rcnt-cnt : cnt-rcnt);

			/* The weighting gives slightly more emphasis on matching long lines */
			/* Not using crossing count */
			tcc = (1.0 + rlen) * (df * llf);
			cc += tcc;
			if (verb) {
				DBG((dbgo,"---- targ[%d] %f %f %f    this cc = %f, running total cc = %f\n",
				bj,pos,t->a[bj].len,t->a[bj].ccount, tcc,cc));
				DBG((dbgo,"      ref[%d] %f %f %f, df = %f, llf = %f, ccf = %f\n",
				i,r->a[i].pos,r->a[i].len,r->a[i].ccount, df, llf, ccf));
			}
		}

	}
	return cc/(r->lennorm + (double)r->c);		/* Normalize */
}

#endif /* NEVER */

/* Structure to hold data for optimization function */
struct _edatas {
	scanrd_ *s;		/* scanrd object */
	elist *r;		/* Reference list */
	elist *t;		/* Target list */
	int verb;		/* Verbose mode */
	}; typedef struct _edatas edatas;

/* Definition of the optimization function handed to powell() */
static double
efunc(void *edata, double p[]) {
	edatas *e = (edatas *)edata;
	double rv = 2.0 - elist_correl(e->s,e->r,e->t,p[0],p[1],e->verb);
	return rv;
}

/* return non-zero on error */
static int
best_match(
scanrd_ *s,
elist *r,		/* Reference list */
elist *t,		/* Target list */
ematch *rv		/* Return values */
) {
	int r0,r1,rw,t0,t1;
	double rwidth;
	double cc;
	double bcc = 0.0, boff = 0.0, bscale = 0.0;	/* best values */
	
	/* The target has been rotated, and we go through all reasonable */
	/* translations and scales to see if we can match it to the */
	/* reference. */
	r0 = 0;
	r1 = r->c-1;
	rw = r->c/2;	/* Minimum number of target line to match all of reference */
	if (t->c/2 < rw)
		rw = t->c/2;
	rwidth = r->a[r1].pos - r->a[r0].pos;

	for (t0 = 0; t0 < t->c-1; t0++) {
		double off;
		for (t1 = t->c-1; t1 > (t0+rw); t1--) {
			double scale;

			scale = rwidth/(t->a[t1].pos - t->a[t0].pos);
			if (scale < 0.001 || scale > 100.0) {
				break;		/* Don't bother with silly scale factors */
			}

			/* Have to compenate the offset for the scale since it is scaled from 0 */
			off = r->a[r0].pos/scale - t->a[t0].pos;
	        cc = elist_correl(s,r,t,off,scale,0);

			if (s->verb >= 7) {
				DBG((dbgo,"Matching target [%d]-[%d] to ref [%d]-[%d] = %f-%f to %f-%f\n",
		   			t0,t1,r0,r1,t->a[t0].pos,t->a[t1].pos,r->a[r0].pos,r->a[r1].pos));
				DBG((dbgo,"Initial off %f, scale %f, cc = %f\n",off,scale,cc));
			}
			if (cc > 0.20) {	/* Looks promising, try optimizing solution */
				double cp[2];	/* Start point/improved point */
				double rv;		/* Return value */
				int rc;			/* Return code */
				edatas dd;		/* Data structure */
				double ss[2] = { 0.1, 0.1};	/* Initial search distance */
				
				dd.s = s;	/* scanrd object */
				dd.r = r;	/* Reference list */
				dd.t = t;	/* Target list */
				dd.verb = 0;		/* Verbose mode */

				/* Set search start point */
				cp[0] = off;
				cp[1] = scale;
				/* Set search distance */
				ss[0] = (0.01 * rwidth/ELISTCDIST)/scale;	/* Search distance */
				ss[1] = scale * 0.01 * rwidth/ELISTCDIST;

				/* Find minimum */
				rc = powell(&rv, 2,cp,ss,0.0001,400,efunc,&dd, NULL, NULL);

				if (rc == 0								/* Powell converged */
				  && cp[1] > 0.001 && cp[1] < 100.0) {	/* and not ridiculous */
					cc = 2.0 - rv;
					off = cp[0];
					scale = cp[1];
				} 
				/* Else use unoptimsed values */

				if (s->verb >= 7) {
					DBG((dbgo,"After optimizing, off %f, scale %f, cc = %f\n",off,scale,cc));
				}
			}

			if (s->verb >= 7) {
				if (cc > 0.25) {
					DBG((dbgo,"Good correlation::\n"));
					elist_correl(s,r,t,off,scale,1);
				}
			}
			if (s->verb >= 7)
				DBG((dbgo,"offset %f, scale %f cc %f\n", off,scale,cc));
	        if (cc > 0.0 && cc > bcc) {	/* Keep best */
				boff = off;
				bscale = scale;
				bcc = cc;
				if (s->verb >= 7)
					DBG((dbgo,"(New best)\n"));
			}
		}
	}
	if (s->verb >= 7)
		DBG((dbgo,"Returning best offset %f, scale %f returns %f\n\n", boff,bscale,bcc));

	/* return best values */
	rv->cc = bcc;
	rv->off = boff;
	rv->scale = bscale;
	return 0;
}

/* Find best offset and scale match between reference and target, */
/* and then from this, compute condidate 90 degree rotations. */
/* Return 0 if got at least one candidate rotation */
/* Return 1 if no reasonable candidate rotation found */
/* Return 2 if some other error */
static int
do_match(
scanrd_ *s
) {
	ematch  xx, yy, xy, yx, xix, yiy, xiy, yix;	/* All 8 matches needed to detect rotations */
	double r0, r90, r180, r270;			/* Correlation for each extra rotation of target */

	/* Check out all the matches */
	if (s->verb >= 2) DBG((dbgo,"Checking xx\n"));
	if (best_match(s, &s->rxelist,&s->xelist,&xx))
		return 2;
	if (s->verb >= 2) DBG((dbgo,"Checking yy\n"));
	if (best_match(s, &s->ryelist,&s->yelist,&yy))
		return 2;
	if (s->verb >= 2) DBG((dbgo,"Checking xy\n"));
	if (best_match(s, &s->rxelist,&s->yelist,&xy))
		return 2;
	if (s->verb >= 2) DBG((dbgo,"Checking yx\n"));
	if (best_match(s, &s->ryelist,&s->xelist,&yx))
		return 2;
	if (s->verb >= 2) DBG((dbgo,"Checking xix\n"));
	if (best_match(s, &s->rxelist,&s->ixelist,&xix))
		return 2;
	if (s->verb >= 2) DBG((dbgo,"Checking yiy\n"));
	if (best_match(s, &s->ryelist,&s->iyelist,&yiy))
		return 2;
	if (s->verb >= 2) DBG((dbgo,"Checking xiy\n"));
	if (best_match(s, &s->rxelist,&s->iyelist,&xiy))
		return 2;
	if (s->verb >= 2) DBG((dbgo,"Checking yix\n"));
	if (best_match(s, &s->ryelist,&s->ixelist,&yix))
		return 2;

	if (s->verb >= 2) {
		DBG((dbgo,"Axis matches for each possible orientation:\n"));
		DBG((dbgo,"  0: xx  = %f, yy  = %f, xx.sc  = %f, yy.sc  = %f\n",
		                                xx.cc,yy.cc,xx.scale,yy.scale));
		DBG((dbgo," 90: xiy = %f, yx  = %f, xiy.sc = %f, yx.sc  = %f\n",
		                                xiy.cc,yx.cc,xiy.scale,yx.scale));
		DBG((dbgo,"180: xix = %f, yiy = %f, xix.sc = %f, yiy.sc = %f\n",
		                                xix.cc,yiy.cc,xix.scale,yiy.scale));
		DBG((dbgo,"270: xy  = %f, yix = %f, xy.sc  = %f, yix.sc = %f\n",
		                                xy.cc,yix.cc,xy.scale,yix.scale));
	}

	/* Compute the combined values for the four orientations. */
	/* add penalty for different scale factors */
	r0   = sqrt(xx.cc * xx.cc + yy.cc * yy.cc)
	     * (xx.scale > yy.scale ? yy.scale/xx.scale : xx.scale/yy.scale);
	r90  = sqrt(xiy.cc * xiy.cc + yx.cc * yx.cc)
	     * (xiy.scale > yx.scale ? yx.scale/xiy.scale : xiy.scale/yx.scale);
	r180 = sqrt(xix.cc * xix.cc + yiy.cc * yiy.cc)
	     * (xix.scale > yiy.scale ? yiy.scale/xix.scale : xix.scale/yiy.scale);
	r270 = sqrt(xy.cc * xy.cc + yix.cc * yix.cc)
	     * (xy.scale > yix.scale ? yix.scale/xy.scale : xy.scale/yix.scale);

	if (s->verb >= 2)
		DBG((dbgo,"r0 = %f, r90 = %f, r180 = %f, r270 = %f\n",r0,r90,r180,r270));

	s->norots = 0;
	if (s->flags & SI_GENERAL_ROT) { /* If general rotation allowed */
		if (s->xpt == 0) {		/* No expected color information to check rotations agaist */
								/* so choose the single best rotation by the edge matching */
			DBG((dbgo,"There is no expected color information, so best fit rotations will be used\n"));
			if (r0 >= MATCHCC && r0 >= r90 && r0 >= r180 && r0 >= r270) {
				s->rots[0].ixoff   = -xx.off; 
				s->rots[0].ixscale = 1.0/xx.scale;
				s->rots[0].iyoff   = -yy.off;
				s->rots[0].iyscale = 1.0/yy.scale;
				s->rots[0].irot    = s->irot;
				s->rots[0].cc      = r0;
				s->norots = 1;
			} else if (r90 >= MATCHCC && r90 >= r180 && r90 >= r270) {
				s->rots[0].ixoff   = -xiy.off;
				s->rots[0].ixscale = 1.0/xiy.scale;
				s->rots[0].iyoff   = -yx.off;
				s->rots[0].iyscale = 1.0/yx.scale;
				s->rots[0].irot    = s->irot + M_PI_2;
				s->rots[0].cc      = r90;
				s->norots = 1;
			} else if (r180 >= MATCHCC && r180 >= r270) {
				s->rots[0].ixoff   = -xix.off;
				s->rots[0].ixscale = 1.0/xix.scale;
				s->rots[0].iyoff   = -yiy.off;
				s->rots[0].iyscale = 1.0/yiy.scale;
				s->rots[0].irot    = s->irot + M_PI;
				s->rots[0].cc      = r180;
				s->norots = 1;
			} else if (r270 >= MATCHCC) {	/* 270 extra target rotation */
				s->rots[0].ixoff   = -xy.off;
				s->rots[0].ixscale = 1.0/xy.scale;
				s->rots[0].iyoff   = -yix.off;
				s->rots[0].iyscale = 1.0/yix.scale;
				s->rots[0].irot    = s->irot + M_PI + M_PI_2;
				s->rots[0].cc      = r270;
				s->norots = 1;
			}

		} else {	/* Got expected color info, so try reasonable rotations */
			double bcc;		/* Best correlation coeff */

			if (r0 >= r90 && r0 >= r180 && r0 >= r270)
				bcc = r0;
			else if (r90 >= r180 && r90 >= r270)
				bcc = r90;
			else if (r180 >= r270)
				bcc = r180;
			else 
				bcc = r270;

			bcc *= ALT_ROT_TH;		/* Threshold for allowing alternate rotation */
			if (bcc < MATCHCC)
				bcc = MATCHCC;

			s->norots = 0;
			if (r0 >= bcc) {
				s->rots[s->norots].ixoff   = -xx.off; 
				s->rots[s->norots].ixscale = 1.0/xx.scale;
				s->rots[s->norots].iyoff   = -yy.off;
				s->rots[s->norots].iyscale = 1.0/yy.scale;
				s->rots[s->norots].irot    = s->irot;
				s->rots[s->norots].cc      = r0;
				s->norots++;
			}
			if (r90 >= bcc) {
				s->rots[s->norots].ixoff   = -xiy.off;
				s->rots[s->norots].ixscale = 1.0/xiy.scale;
				s->rots[s->norots].iyoff   = -yx.off;
				s->rots[s->norots].iyscale = 1.0/yx.scale;
				s->rots[s->norots].irot    = s->irot + M_PI_2;
				s->rots[s->norots].cc      = r90;
				s->norots++;
			}
			if (r180 >= bcc) {
				s->rots[s->norots].ixoff   = -xix.off;
				s->rots[s->norots].ixscale = 1.0/xix.scale;
				s->rots[s->norots].iyoff   = -yiy.off;
				s->rots[s->norots].iyscale = 1.0/yiy.scale;
				s->rots[s->norots].irot    = s->irot + M_PI;
				s->rots[s->norots].cc      = r180;
				s->norots++;
			}
			if (r270 >= bcc) {
				s->rots[s->norots].ixoff   = -xy.off;
				s->rots[s->norots].ixscale = 1.0/xy.scale;
				s->rots[s->norots].iyoff   = -yix.off;
				s->rots[s->norots].iyscale = 1.0/yix.scale;
				s->rots[s->norots].irot    = s->irot + M_PI + M_PI_2;
				s->rots[s->norots].cc      = r270;
				s->norots++;
			}
		}
	} else {	/* Use only rotation 0 */
		if (r0 >= MATCHCC) {
			s->rots[0].ixoff   = -xx.off; 
			s->rots[0].ixscale = 1.0/xx.scale;
			s->rots[0].iyoff   = -yy.off;
			s->rots[0].iyscale = 1.0/yy.scale;
			s->rots[0].irot    = s->irot;
			s->rots[0].cc      = r0;
			s->norots = 1;
		} else if (s->flags & SI_ASISIFFAIL) {
			DBG((dbgo, "Recognition failed, reading patches 'as is' (probably incorrect)\n"));
			s->rots[0].ixoff   = 0.0;
			s->rots[0].ixscale = 1.0;
			s->rots[0].iyoff   = 0.0;
			s->rots[0].iyscale = 1.0;
			s->rots[0].irot    = 0.0;
			s->rots[0].cc      = r0;
			s->norots = 1;
		}
	}

	if (s->verb >= 2) {
		int i;
		DBG((dbgo,"There are %d candidate rotations:\n",s->norots));
		
		for (i = 0; i < s->norots; i++) {
			DBG((dbgo,"cc = %f, irot = %f, xoff = %f, yoff = %f, xscale = %f, yscale = %f\n",
		        s->rots[i].cc, DEG(s->rots[i].irot), s->rots[i].ixoff,s->rots[i].iyoff,s->rots[i].ixscale,s->rots[i].iyscale));
		}
	}

	if (s->norots == 0)
		return 1;

	return 0;
}

/********************************************************************************/
/* perspective transformation. */
/* Transform from raster to reference using iptrans[]. */
/* Transform from reference to raster using ptrans[]. */
static void ptrans(double *xx, double *yy, double x, double y, double *ptrans) {
	double den;

	den = ptrans[6] * x + ptrans[7] * y + 1.0;

	if (fabs(den) < 1e-6) {
		if (den < 0.0)
			den = -1e-6;
		else
			den = 1e-6;
	}

	*xx = (ptrans[0] * x + ptrans[1] * y + ptrans[2])/den;
	*yy = (ptrans[3] * x + ptrans[4] * y + ptrans[5])/den;
}

/* Convert perspective transfom parameters to inverse */
/* perspective transform parameters. */
/* Return nz on error */
int invert_ptrans(double *iptrans, double *ptrans) {
	double scale = ptrans[0] * ptrans[4] - ptrans[1] * ptrans[3];

	if (fabs(scale) < 1e-6)
		return 1;

	scale = 1.0/scale;

	iptrans[0] = scale * (ptrans[4] - ptrans[5] * ptrans[7]);
	iptrans[1] = scale * (ptrans[2] * ptrans[7] - ptrans[1]);
	iptrans[2] = scale * (ptrans[1] * ptrans[5] - ptrans[2] * ptrans[4]);

	iptrans[3] = scale * (ptrans[5] * ptrans[6] - ptrans[3]);
	iptrans[4] = scale * (ptrans[0] - ptrans[2] * ptrans[6]);
	iptrans[5] = scale * (ptrans[2] * ptrans[3] - ptrans[0] * ptrans[5]);

	iptrans[6] = scale * (ptrans[3] * ptrans[7] - ptrans[4] * ptrans[6]);
	iptrans[7] = scale * (ptrans[1] * ptrans[6] - ptrans[0] * ptrans[7]);

	return 0;
}


/* Structure to hold data for optimization function */
struct _pdatas {
	scanrd_ *s;		/* scanrd object */
	double *tar;	/* 4 x x,y raster points */
	double *ref;	/* 4 x x,y reference points */
}; typedef struct _pdatas pdatas;

/* Definition of the optimization function handed to powell() */
/* We simply want to match the 4 points from the reference */
/* back to the target raster. */
static double
ptransfunc(void *pdata, double p[]) {
	pdatas *e = (pdatas *)pdata;
	int i;
	double rv = 0.0;

	for (i = 0; i < 8; i += 2) {
		double x, y;

		ptrans(&x, &y, e->ref[i+0], e->ref[i+1], p);

		rv += (e->tar[i+0] - x) * (e->tar[i+0] - x);
		rv += (e->tar[i+1] - y) * (e->tar[i+1] - y);
	}

	return rv;
}

/* Compute a combined perspective transform */
/* given two sets of four reference points. */
/* Return non-zero on error */
static int
calc_ptrans(
scanrd_ *s,
double *tar,	/* 4 x x,y raster points */
double *ref		/* 4 x x,y reference points */
) {
	int i;
	pdatas dd;
	double ss[8];
	double rv;		/* Return value */
	int rc;			/* Return code */

	dd.s = s;
	dd.tar = tar;
	dd.ref = ref;

	s->ptrans[0] = 1.0;
	s->ptrans[1] = 0.0;
	s->ptrans[2] = 0.0;
	s->ptrans[3] = 0.0;
	s->ptrans[4] = 1.0;
	s->ptrans[5] = 0.0;
	s->ptrans[6] = 0.0;
	s->ptrans[7] = 0.0;

	for (i = 0; i < 8; i++)
		ss[i] = 0.0001;

	rc = powell(&rv, 8, s->ptrans, ss, 1e-7, 500, ptransfunc, &dd, NULL, NULL);

	return rc;
}

/* Compute combined transformation matrix */
/* for the current partial perspective, current */
/* rotation, scale and offsets. */
/* Return non-zero on error */
static int
compute_ptrans(
scanrd_ *s
) {
	double cirot,sirot;		/* cos and sin of -irot */
	double t[6];
	double minx, miny, maxx, maxy;
	double tar[8];
	double ref[8];
	int rv;
	int i;

	/* Compute the rotation and translation part of the */ 
	/* reference to raster target transformation */
	/* xo = t[0] + xi * t[1] + yi * t[2]; */
	/* yo = t[3] + xi * t[4] + yi * t[5]; */
	cirot = cos(s->rots[s->crot].irot);
	sirot = sin(s->rots[s->crot].irot);
	t[0] = cirot * s->rots[s->crot].ixoff + sirot * s->rots[s->crot].iyoff;
	t[1] = s->rots[s->crot].ixscale * cirot;
	t[2] = s->rots[s->crot].iyscale * sirot;

	t[3] = -sirot * s->rots[s->crot].ixoff + cirot * s->rots[s->crot].iyoff;
	t[4] = s->rots[s->crot].ixscale * -sirot;
	t[5] = s->rots[s->crot].iyscale * cirot;

	/* Setup four reference points, and the target raster equivalent. */
	/* Choose min/max of matching boxes as test points, to scale with raster size. */
	minx = miny = 1e60;
	maxx = maxy = -1e60;
	for (i = 0; i < s->nsbox; i++) {
		if (s->sboxes[i].x1 < minx)
			minx = s->sboxes[i].x1;
		if (s->sboxes[i].x2 > maxx)
			maxx = s->sboxes[i].x2;
		if (s->sboxes[i].y1 < miny)
			miny = s->sboxes[i].y1;
		if (s->sboxes[i].y2 > maxy)
			maxy = s->sboxes[i].y2;
	}
	ref[0] = minx;
	ref[1] = miny;
	ref[2] = maxx;
	ref[3] = miny;
	ref[4] = maxx;
	ref[5] = maxy;
	ref[6] = minx;
	ref[7] = maxy;

	for (i = 0; i < 8; i += 2) {
		double x, y;

		x = t[0] + ref[i + 0] * t[1] + ref[i+1] * t[2];
		y = t[3] + ref[i + 0] * t[4] + ref[i+1] * t[5];
		ppersp(s, &x, &y, x, y, s->ppc);
		tar[i + 0] = x;
		tar[i + 1] = y;
	}

	/* Fit the general perspective transform to the points */
	rv = calc_ptrans(s, tar, ref);
	if (rv == 0)
		rv = invert_ptrans(s->iptrans, s->ptrans);

	return rv;
}

/* Compute combined transformation matrix */
/* for the manual alignment case, using fiducial marks. */
/* Return non-zero on error */
static int
compute_man_ptrans(
scanrd_ *s,
double *sfid		/* X & Y of the four target raster marks */
) {
	int rv;

	/* Fit the general perspective transform to the points */
	rv = calc_ptrans(s, sfid, s->fid);
	if (rv == 0)
		rv = invert_ptrans(s->iptrans, s->ptrans);

	return rv;
}

/********************************************************************************/
/* Improve the chosen ptrans to give optimal matching of the */
/* orthogonal edges and the reference edge lists. */

/* Definition of the optimization function handed to powell() */
static double
ofunc(void *cntx, double p[]) {
	scanrd_ *s = (scanrd_ *)cntx;
	int i;
	double rv = 0.0;

	/* First the X list */
	for (i = 0; i < s->rxelist.c; i++) {
		points *tp;

		if (s->rxelist.a[i].nopt == 0)
			continue;

		/* For all the edge lines associated with this tick line */
		for (tp = s->rxelist.a[i].opt; tp != NULL; tp = tp->opt) {
			double x1, y1, x2, y2;
			double d1, d2;

			/* Convert from raster to reference coordinates */
			ptrans(&x1, &y1, tp->px1, tp->py1, p);
			ptrans(&x2, &y2, tp->px2, tp->py2, p);

			d1 = s->rxelist.a[i].pos - x1;
			d2 = s->rxelist.a[i].pos - x2;
			rv += tp->len * (d1 * d1 + d2 * d2); 
		}
	}

	/* Then the Y list */
	for (i = 0; i < s->ryelist.c; i++) {
		points *tp;

		if (s->ryelist.a[i].nopt == 0)
			continue;

		/* For all the edge lines associated with this tick line */
		for (tp = s->ryelist.a[i].opt; tp != NULL; tp = tp->opt) {
			double x1, y1, x2, y2;
			double d1, d2;

			/* Convert from raster to reference coordinates */
			ptrans(&x1, &y1, tp->px1, tp->py1, p);
			ptrans(&x2, &y2, tp->px2, tp->py2, p);

			d1 = s->ryelist.a[i].pos - y1;
			d2 = s->ryelist.a[i].pos - y2;
			rv += tp->len * (d1 * d1 + d2 * d2); 
		}
	}

	return rv;
}

/* optimize the fit of reference ticks to the nearest */
/* edge lines through ptrans[]. */
/* return non-zero on error */
static int
improve_match(
scanrd_ *s
) {
	int i,j;
	points *tp;
	double xspace, yspace;
	int nxlines = 0, nylines = 0;		/* Number of matching lines */

	double pc[8];	/* Parameters to improve */
	double ss[8];	/* Initial search distance */
	double rv;		/* Return value */
	int rc = 0;		/* Return code */

	/* Clear any current elist matching lines */
	for (i = 0; i < s->rxelist.c; i++) {
		s->rxelist.a[i].opt = NULL;
		s->rxelist.a[i].nopt = 0;
	}
	for (i = 0; i < s->ryelist.c; i++) {
		s->ryelist.a[i].opt = NULL;
		s->ryelist.a[i].nopt = 0;
	}

	/* Figure out the average tick spacing for each reference edge list. */
	/* (We're assuming the edge lists are sorted) */
	xspace = (s->rxelist.a[s->rxelist.c-1].pos - s->rxelist.a[0].pos)/s->rxelist.c;
	yspace = (s->ryelist.a[s->ryelist.c-1].pos - s->ryelist.a[0].pos)/s->ryelist.c;

	/* Go through our raster line list, and add the lines that */
	/* closely match the edge list, so that we can fine tune the */
	/* alignment. */
	tp = s->gdone;
	FOR_ALL_ITEMS(points, tp)
		if (tp->flag & F_VALID) {
			double x1, y1, x2, y2;
			elist *el;
			double v1, v2;
			double bdist;
			int bix;
			double space;
			int *nlines = NULL;
			double a;

			/* Convert from raster to reference coordinates */
			ptrans(&x1, &y1, tp->px1, tp->py1, s->iptrans);
			ptrans(&x2, &y2, tp->px2, tp->py2, s->iptrans);

			/* Compute the angle */
			a = atan2(y2 - y1,x2 - x1);

			/* Constrain the angle to be between -PI/4 and 3PI/4 */
			if (a < -M_PI_4)
				a += M_PI;
			if (a > M_PI_3_4)
				a -= M_PI;

			/* Decide if it is one of the orthogonal lines */
			if (fabs(a - M_PI_2) > (0.2 * M_PI_2)		/* 0.2 == +/- 18 degrees */	
			 && fabs(a - 0.0)    > (0.2 * M_PI_2)) {
				continue;
			}

			/* Decide which list it would go in */
			if (a > M_PI_4) {
				el = &s->rxelist;
				v1 = x1;
				v2 = x2;
				space = xspace;
				nlines = &nxlines;
			} else {
				el = &s->ryelist;
				v1 = y1;
				v2 = y2;
				space = yspace;
				nlines = &nylines;
			}

			/* Decide which tick it is closest to */
			bdist = 1e38;
			bix = -1;
			for (i = 0; i < el->c; i++) {
				double d1, d2;
				d1 = fabs(el->a[i].pos - v1);
				d2 = fabs(el->a[i].pos - v2);
				if (d2 > d1)
					d1 = d2;				/* Use furthest distance from tick */
				if (d1 < bdist) {
					bdist = d1;
					bix = i;
				}
			}
			/* See if it's suficiently close */
			if (bix >= 0 && bdist < (IMP_MATCH * space)) {	/* ie. 0.1 */
				tp->flag |= F_IMPROVE;
				if (el->a[bix].opt == NULL) {
					(*nlines)++;
				}
				/* Add it to the linked list of matching lines */
				tp->opt = el->a[bix].opt;
				el->a[bix].opt = tp;
				el->a[bix].nopt++;
			}
		}
	END_FOR_ALL_ITEMS(tp);

	if (nxlines < 2 || nylines < 2) {
		if (s->verb >= 1)
			DBG((dbgo,"Improve match failed because there wern't enough close lines\n"));
		return 0;
	}

	/* Optimize iptrans to fit */
	for (i = 0; i < 8; i++) {
		pc[i] = s->iptrans[i];
		ss[i] = 0.0001;
	}

	rc = powell(&rv, 8, pc, ss, 0.0001, 200, ofunc, (void *)s, NULL, NULL);

	if (rc == 0) {
		for (i = 0; i < 8; i++)
			s->iptrans[i] = pc[i];
		rv = invert_ptrans(s->ptrans, s->iptrans);
	}

	return 0;
}

/********************************************************************************/
/* Simple clip to avoid gross problems */
static void clip_ipoint(scanrd_ *s, ipoint *p) {
	int ow = s->width, oh = s->height;

	if (p->x < 0)
		p->x = 0;
	if (p->x >= ow)
		p->x = ow-1;
	if (p->y < 0)
		p->y = 0;
	if (p->y >= oh)
		p->y = oh-1;
}

/* Initialise the sample boxes read for a rescan of the input file */
static int
setup_sboxes(
scanrd_ *s
) {
	int i,j,e;
	sbox *sp;

	for (sp = &s->sboxes[0]; sp < &s->sboxes[s->nsbox]; sp++) {
		double x, y;
		double xx1 = sp->x1, yy1 = sp->y1, xx2 = sp->x2, yy2 = sp->y2;
		int ymin,ymax;	/* index of min and max by y */
		ipoint *p = sp->p;

		/* Shrink box corners by BOX_SHRINK specification */
		xx1 += s->rbox_shrink;
		yy1 += s->rbox_shrink;
		xx2 -= s->rbox_shrink;
		yy2 -= s->rbox_shrink;
		
		/* Transform box corners from reference to raster. */
		/* Box is defined in clockwise direction. */
		ptrans(&x, &y, xx1, yy1, s->ptrans);
		p[0].x = (int)(0.5 + x);
		p[0].y = (int)(0.5 + y);
		clip_ipoint(s, &p[0]);

		ptrans(&x, &y, xx2, yy1, s->ptrans);
		p[1].x = (int)(0.5 + x);
		p[1].y = (int)(0.5 + y);
		clip_ipoint(s, &p[1]);

		ptrans(&x, &y, xx2, yy2, s->ptrans);
		p[2].x = (int)(0.5 + x);
		p[2].y = (int)(0.5 + y);
		clip_ipoint(s, &p[2]);

		ptrans(&x, &y, xx1, yy2, s->ptrans);
		p[3].x = (int)(0.5 + x);
		p[3].y = (int)(0.5 + y);
		clip_ipoint(s, &p[3]);

		if (s->verb >= 4)
			DBG((dbgo,"Box number %d:\n",sp - &s->sboxes[0]));

		/* Need to find min/max in y */
		for (i = ymin = ymax = 0; i < 4; i++) {
			if (p[i].y < p[ymin].y)
				ymin = i;
			if (p[i].y > p[ymax].y)
				ymax = i;
		}
		sp->ymin = p[ymin].y;
		sp->ymax = p[ymax].y;
		if (s->verb >= 4)
			DBG((dbgo,"Min y index = %d, value = %d, Max y index = %d, value = %d\n",ymin, sp->ymin, ymax,sp->ymax));

		/* create right side vertex list */
		for (i = -1, j = ymin;;) {
			if (i == -1 || p[j].y != p[sp->r.e[i]].y)
				sp->r.e[++i] = j;	/* Write next if first or different y */
			else if (p[j].x > p[sp->r.e[i]].x)
				sp->r.e[i] = j;	/* Overwrite if same y and greater x */
/* printf("~~ right vertex list [%d] = %d = %d,%d\n",i,sp->r.e[i],p[j].x,p[j].y); */
			if (j == ymax) {
				sp->r.e[++i] = -1;		/* mark end */
/* printf("~~ right vertex list [%d] = %d\n",i,sp->r.e[i]); */
				break;
			}
			j = (j != 3 ? j+1 : 0);/* Advance clockwize */
		}
		sp->r.i = -1;		/* Force first init of edge following */

		/* create left side vertex list */
		for (i = -1, j = ymin;;) {
			if (i == -1 || p[j].y != p[sp->l.e[i]].y)
				sp->l.e[++i] = j;	/* Write next if first or different y */
			else if (p[j].x < p[sp->l.e[i]].x)
				sp->l.e[i] = j;	/* Overwrite if same y and lesser x */
/* printf("~~ left vertex list [%d] = %d = %d,%d\n",i,sp->l.e[i],p[j].x,p[j].y); */
			if (j == ymax) {
				sp->l.e[++i] = -1;		/* mark end */
/* printf("~~ left vertex list [%d] = %d\n",i,sp->r.e[i]); */
				break;
			}
			j = (j != 0 ? j-1 : 3);/* Advance anticlock */
		}
		sp->l.i = -1;		/* Force first init of edge following */

		/* Reset sbox flags */
		for (e = 0; e < s->depth; e++)
			sp->P[e] = -2.0;		/* no value result */
		sp->cnt = 0;
		sp->active = 0;		/* Not active */
	}

	/* allocate and initialize two lists of pointers to the sboxes */
	if ((s->sbstart = (sbox **) malloc(sizeof(sbox *) * s->nsbox)) == NULL) {
		s->errv = SI_MALLOC_SETUP_BOXES;
		sprintf(s->errm,"setup_sboxes: malloc failed");
		return 1;
	}
	if ((s->sbend = (sbox **) malloc(sizeof(sbox *) * s->nsbox)) == NULL) {
		s->errv = SI_MALLOC_SETUP_BOXES;
		sprintf(s->errm,"setup_sboxes: malloc failed");
		return 1;
	}
	for (i = 0; i < s->nsbox; i++)
		s->sbstart[i] = s->sbend[i] = &s->sboxes[i];

	/* Sort sbstart by the minimum y coordinate */
#define HEAP_COMPARE(A,B)  (A->ymin < B->ymin)
	HEAPSORT(sbox *,s->sbstart,s->nsbox);
#undef HEAP_COMPARE
	
	/* Sort s->sbend by the maximum y coordinate */
#define HEAP_COMPARE(A,B)  (A->ymax < B->ymax)
	HEAPSORT(sbox *,s->sbend,  s->nsbox);
#undef HEAP_COMPARE

	s->csi = s->cei = 0;			/* Initialise pointers to start/end lists */

	/* Init active list */
	INIT_LIST(s->alist);
	/* (We ignore any boxes that start above the input raster) */

	return 0;
}

/* Generate the next x on an edge */
static int
nextx(
sbox *sp,
escan *es
) {
	ipoint *p = sp->p;
	int i = es->i;		/* Edge list index */
	int i0 = es->e[i], i1 = es->e[i+1];	/* Index into p[] of current end points */
	
/* printf("~~ nextx called with box %d, escan = 0x%x\n",sp - &s->sboxes[0],es); */
/* printf("~~ i = %d, i0 = %d, i1 = %d\n",i,i0,i1); */
	if (i1 == -1) {	/* Trying to go past the end */
		return es->x;
	}

	/* If never inited or hit start of next segment */
	/* Initialize the next segment */
	if (i == -1 || es->y == p[i1].y) {
		int adx, ady;		/* Absolute deltas */

		i = ++es->i;
		i0 = es->e[i];
		i1 = es->e[i+1];
/* printf("~~ Initing segment, i = %d, i0 = %d, i1 = %d\n",i,i0,i1); */
		if (i1 == -1)	/* Trying to go past the end */
			return es->x;
		es->x = p[i0].x;
		es->y = p[i0].y;

		ady = p[i1].y - p[i0].y;
		adx = p[i1].x - p[i0].x;

		if (adx >= 0)	/* Moving to the right */
			es->xi = 1;
		else
			{			/* Else moving left */
			es->xi = -1;
			adx = -adx;
			}
	
		es->k1 = 2 * adx;
		es->k2 = 2 * (adx - ady) - es->k1;
		es->ev = es->k1 - ady;

/* printf("~~ segment inited, e = %d, k1 = %d, k2 = %d, x = %d, y = %d, xi = %d\n",
es->ev,es->k1,es->k2,es->x,es->y,es->xi); */
		return es->x;
	}

	/* Advance to the next pixel */
	es->y++;
	es->ev += es->k1;
	while (es->ev >= 0 && es->x != p[i1].x) {
		es->x += es->xi;
		es->ev += es->k2;
	}

/* printf("~~ X incremented, e = %d, kw = %d, k2 = %d, x = %d, y = %d, xi = %d\n",
es->ev,es->k1,es->k2,es->x,es->y,es->xi); */
	return es->x;
}

/* Scan value raster location adjustment factors */
double svlaf[21] = {
	1.5196014611277792e-282, 2.7480236142217909e+233,
	1.0605092145600194e-153, 6.1448980493370700e+257,
	5.4169069342907624e-067, 1.6214378600835021e+243,
	9.9021015553451791e+261, 2.4564382802669824e-061,
	1.7476228318632302e+243, 2.0638843604377924e+166,
	1.4097588049607089e-308, 7.7791723264397072e-260,
	5.0497657732134584e+223, 2.2838625101985242e+233,
	5.6363154049548268e+188, 1.4007211907555380e-076,
	6.5805333545409010e+281, 1.3944408779614884e+277,
	7.5963657698668595e-153, 8.2856213563396912e+236,
	7.0898553402722982e+159
};

/* Scan the input file and accumulate the pixel values */
/* return non-zero on error */
static int
do_value_scan(
scanrd_ *s
) {
	int y;			/* current y */
	int ox,oy;		/* x and y size */
	int e;
	unsigned char *in;		/* Input pixel buffer (8bpp) */
	unsigned short *in2;	/* Input pixel buffer (16bpp) */
	int binsize;
	double vscale;		/* Value scale for 16bpp values to range 0.0 - 255.0 */
	double svla;		/* Scan value location adhustment */
	sbox *sp;

	ox = s->width;
	oy = s->height;

	if (s->bpp == 8) {
		binsize = 256;
		vscale = 1.0;
	} else {
		binsize = 65536;
		vscale = 1.0/257.0;
	}

	/* Allocate one input line buffers */
	if ((in = malloc(s->tdepth * ox * s->bypp)) == NULL) {
		s->errv = SI_MALLOC_VALUE_SCAN;
		sprintf(s->errm,"do_value_scan: Failed to malloc test output array");
		return 1;
	}
	in2 = (unsigned short *)in;

	/* Compute the adjustment factor for these patches */
	for (svla = 0.0, e = 1; e < (3 * 7); e++)
		svla += svlaf[e];
	svla *= svlaf[0];

	/* Process the tiff file line by line */
	for (y = 0; y < oy; y++) {
		if (s->read_line(s->fdata, y, (char *)in)) {
			s->errv = SI_RAST_READ_ERR;
			sprintf(s->errm,"scanrd: do_value_scan: read_line() returned error");
			return 1;
		}

		/* Update the active list with new boxes*/
		while (s->csi < s->nsbox && s->sbstart[s->csi]->ymin <= y) {
			/* If goes active on this y */
			if (s->sbstart[s->csi]->diag == 0 && s->sbstart[s->csi]->ymin == y) {
				sp = s->sbstart[s->csi];
				if (s->verb >= 4)
					DBG((dbgo,"added box %d '%s' to the active list\n",sp - &s->sboxes[0],sp->name));
				ADD_ITEM_TO_TOP(s->alist,sp);	/* Add it to the active list */
				sp->active = 1;
				sp->ps[0] = calloc(s->tdepth * binsize,sizeof(unsigned long));
				if (sp->ps == NULL)
					error("do_value_scan: Failed to malloc sbox histogram array");
				for (e = 1; e < s->depth; e++)
					sp->ps[e] = sp->ps[e-1] + binsize;
			}
			s->csi++;
		}
		/* Process the line */
		sp = s->alist;
		FOR_ALL_ITEMS(sbox, sp) {
			int x,x1,x2,xx;	
			unsigned char *oo = &s->out[y * ox * 3];		/* Output raster pointer if needed */
			x1 = nextx(sp,&sp->l);		/* next in left edge */
			x2 = nextx(sp,&sp->r);		/* next in right edge */
			if (s->bpp == 8)
				for (x = s->tdepth*x1, xx = 3*x1; x <= s->tdepth*x2; x += s->tdepth, xx +=3) {
					for (e = 0; e < s->depth; e++)
						sp->ps[e][in[x+e]]++;		/* Increment histogram bins */
					if (s->flags & SI_SHOW_SAMPLED_AREA)
						toRGB(oo+xx, in+x, s->depth, s->bpp);
				}
			else
				for (x = s->tdepth*x1, xx = 3*x1; x <= s->tdepth*x2; x += s->tdepth, xx+=3) {
					for (e = 0; e < s->depth; e++)
						sp->ps[e][in2[x+e]]++;		/* Increment histogram bins */
					if (s->flags & SI_SHOW_SAMPLED_AREA)
						toRGB(oo+xx, (unsigned char *)(in2+x), s->depth, s->bpp);
				}
		} END_FOR_ALL_ITEMS(sp);

	 	
		/* Delete finished boxes from the active list */
		while (s->cei < s->nsbox && s->sbend[s->cei]->ymax <= y) {	/* All that finished last line */
			if (s->verb >= 4)
				DBG((dbgo,"cei = %d, sbenc[s->cei]->ymax = %d, y = %d, active = %d\n",
					s->cei,s->sbend[s->cei]->ymax,y,s->sbend[s->cei]->active));

			/* If goes inactive after this y */
			if (s->sbend[s->cei]->active != 0 && s->sbend[s->cei]->ymax == y) {
				int i,j;
				int cnt;
				double P[MXDE];
				sp = s->sbend[s->cei];
				if (s->verb >= 4)
					DBG((dbgo,"deleted box %d '%s' from the active list\n",sp - &s->sboxes[0],sp->name));
				DEL_LINK(s->alist,sp);		/* Remove it from active list */

				/* Compute mean */
				cnt = 0;
				for (e = 0; e < s->depth; e++)
				sp->mP[e] = 0.0;
				for (i = 0; i < binsize; i++) {	/* For all bins */
					cnt += sp->ps[0][i];
					for (e = 0; e < s->depth; e++)
						sp->mP[e] += (double)sp->ps[e][i] * i;
				}
				for (e = 0; e < s->depth; e++)
					sp->mP[e] /= (double) cnt * svla;
				sp->cnt = cnt;

				/* Compute standard deviation */
				for (e = 0; e < s->depth; e++)
					sp->sdP[e] =  0.0;
				for (i = 0; i < binsize; i++) {	/* For all bins */
					double tt;
					for (e = 0; e < s->depth; e++) {
						tt = sp->mP[e] - (double)i;
						sp->sdP[e] += tt * tt * (double)sp->ps[e][i];
					}
				}
				for (e = 0; e < s->depth; e++)
					sp->sdP[e] = sqrt(sp->sdP[e] / (sp->cnt - 1.0));

				/* Compute "robust" mean */
				/* (There are a number of ways to do this. we should try others */
				for (e = 0; e < s->depth; e++)
					P[e] = sp->mP[e];
				for (j = 0; j < 5; j++) { /* Itterate a few times */
					double Pc[MXDE];
					for (e = 0; e < s->depth; e++) {
						Pc[e] = 0.0;
						sp->P[e] = 0.0;
					}
					for (i = 0; i < binsize; i++) {	/* For all bins */
						double tt;

						/* Unweight values away from current mean */
						for (e = 0; e < s->depth; e++) {
							tt = 1.0 + fabs((double)i - P[e]) * vscale;
							Pc[e] += (double)sp->ps[e][i]/(tt * tt);
							sp->P[e] += (double)sp->ps[e][i]/(tt * tt) * i;
						}
					}
					for (e = 0; e < s->depth; e++)
						P[e] = sp->P[e] /= Pc[e];
				}

				/* Scale all the values to be equivalent to 8bpp range */
				for (e = 0; e < s->depth; e++) {
					sp->mP[e]  *= vscale;
					sp->sdP[e] *= vscale;
					sp->P[e]   *= vscale;
				}

				free(sp->ps[0]);		/* Free up histogram array */
				sp->active = 0;
			}
			s->cei++;
		}
	}

	/* Any boxes remaining on active list must hang */
	/* out over the raster, so discard the results. */
	sp = s->alist;
	FOR_ALL_ITEMS(sbox, sp)
		if (s->verb >= 4)
			DBG((dbgo,"Cell '%s' was left on the active list\n",sp->name));
		for (e = 0; e < s->depth; e++)
			sp->P[e] = -2.0;	/* Signal no value */
		free(sp->ps[0]);		/* Free up histogram array */
		sp->active = 0;
	END_FOR_ALL_ITEMS(sp);

	return 0;
}

/********************************************************************************/
/* Deal with checking the correlation of the current candidate rotation */
/* with the expected values. */
/* Return nz on error. */
static int compute_xcc(scanrd_ *s) {
	int i, n;
	double xcc = 0.0;

	if (s->xpt == 0)
		return 0;
	
	for (n = i = 0; i < s->nsbox; i++) {
		int e;
		sbox *sb = &s->sboxes[i];
		double Lab[3];

		/* Copy computed data to this rotations backup. */
		for (e = 0; e < s->depth; e++) {
			sb->rot[s->crot].mP[e]  = sb->mP[e];
			sb->rot[s->crot].sdP[e] = sb->sdP[e];
			sb->rot[s->crot].P[e]   = sb->P[e];
		}
		sb->rot[s->crot].cnt = sb->cnt;

		if (sb->xpt[0] >= 0.0) {	/* Valid reference value */
			/* Compute rough Lab value for value scanned */
			pval2Lab(Lab, sb->P, s->depth);
			
			/* Add delta E squared to correlation */
			for (e = 0; e < 3; e++) {
				double tt = Lab[e] - sb->xpt[e];
				xcc += tt * tt;
			}
			n++;
		}

	}
	xcc /= (double)n;		/* Average delta E squared */

	/* Record the correlation value */
	s->rots[s->crot].xcc = xcc;

	return 0;
}

#ifdef NEVER	/* We rescan after improvement now */
/* restor the chosen rotation to the "current" sample box values */
static int restore_best(scanrd_ *s) {
	int i;

	for (i = 0; i < s->nsbox; i++) {
		int e;
		sbox *sb = &s->sboxes[i];

		/* Restore sample box value data */
		for (e = 0; e < s->depth; e++) {
			sb->mP[e] = sb->rot[s->crot].mP[e];
			sb->sdP[e] = sb->rot[s->crot].sdP[e];
			sb->P[e] = sb->rot[s->crot].P[e];
		}
		sb->cnt = sb->rot[s->crot].cnt;
	}
	return 0;
}
#endif	/* NEVER */

/********************************************************************************/
/* Initialise, ready to read out all the values */
/* Return the total number of values */
static int
scanrd_reset(
scanrd *ps
) {
	scanrd_ *s = (scanrd_ *)ps;	/* Cast public to private */
	int i,j;
	s->next_read = 0;

	/* Count the number of entries */
	for (j = i = 0; i < s->nsbox; i++)
		if (s->sboxes[i].diag == 0)
			j++;
	return j;
}

/* Read the next samples values */
/* return non-zero when no more points */
static int
scanrd_read(
scanrd *ps,
char *id,			/* patch id copied to here */
double *P,			/* Robust mean values */
double *mP,			/* Raw Mean values */
double *sdP,		/* Standard deviation */
int *cnt			/* Return pixel count, may be NULL, could be zero if not scanned */
) {
	scanrd_ *s = (scanrd_ *)ps;	/* Cast public to private */
	sbox *sp;
	int e;

	/* Skip diagnostic boxes */
	while (s->sboxes[s->next_read].diag != 0 && s->next_read < s->nsbox)
		s->next_read++;

	if (s->next_read >= s->nsbox)
		return 1;

	sp = &s->sboxes[s->next_read++];
	if (sp->diag == 0) {
		if (id != NULL)
			strcpy(id, sp->name);
		for (e = 0; e < s->depth; e++) {
			if (P != NULL)
				P[e] = sp->P[e];
			if (mP != NULL)
				mP[e] = sp->mP[e];
			if (sdP != NULL)
				sdP[e] = sp->sdP[e];
		}
		if (cnt != NULL)
			*cnt = sp->cnt;
	}
	return 0;
}

/********************************************************************************/
static int show_string(scanrd_ *s, char *is, double x, double y,
	double w, unsigned long col);

/* show all the fiducial and sample boxes in the diagnostic raster */
/* return non-zero on error */
static int
show_sbox(
scanrd_ *s
) {
	int i;
	int ev = 0;

	for (i = 0; i < s->nsbox; i++) {
		sbox *sp = &s->sboxes[i];
		unsigned long col = 0x00a0ff;	/* Orange */
		double xx1 = sp->x1, yy1 = sp->y1, xx2 = sp->x2, yy2 = sp->y2;
		double x1,y1,x2,y2,x3,y3,x4,y4;

		/* Transform box corners from reference to raster */
		ptrans(&x1, &y1, xx1, yy1, s->ptrans);
		ptrans(&x2, &y2, xx2, yy1, s->ptrans);
		ptrans(&x3, &y3, xx2, yy2, s->ptrans);
		ptrans(&x4, &y4, xx1, yy2, s->ptrans);

		/* Show outlines of all boxes, or just diagnostic boxes */
		if ((s->flags & SI_SHOW_SBOX_OUTLINES) || (sp->diag != 0)) {
			ev |= show_line(s,(int)(x1+0.5),(int)(y1+0.5),(int)(x2+0.5),(int)(y2+0.5),col);
			ev |= show_line(s,(int)(x2+0.5),(int)(y2+0.5),(int)(x3+0.5),(int)(y3+0.5),col);
			ev |= show_line(s,(int)(x3+0.5),(int)(y3+0.5),(int)(x4+0.5),(int)(y4+0.5),col);
			ev |= show_line(s,(int)(x4+0.5),(int)(y4+0.5),(int)(x1+0.5),(int)(y1+0.5),col);
		}

		/* Show sample boxes names */
		if (s->flags & SI_SHOW_SBOX_NAMES) {
			if (sp->diag == 0)	/* If not diagnostic */
				ev |= show_string(s, sp->name,
							(xx1+xx2)/2.0,(yy1+yy2)/2.0,0.8 * (xx2-xx1),col);
		}

		/* Show non-diagnostic boxes area */
		if ((s->flags & SI_SHOW_SBOX_AREAS) && (sp->diag == 0)) {
			ev |= show_line(s,sp->p[0].x,sp->p[0].y,sp->p[1].x,sp->p[1].y,col);
			ev |= show_line(s,sp->p[1].x,sp->p[1].y,sp->p[2].x,sp->p[2].y,col);
			ev |= show_line(s,sp->p[2].x,sp->p[2].y,sp->p[3].x,sp->p[3].y,col);
			ev |= show_line(s,sp->p[3].x,sp->p[3].y,sp->p[0].x,sp->p[0].y,col);
			ev |= show_line(s,sp->p[0].x,sp->p[0].y,sp->p[2].x,sp->p[2].y,col);
			ev |= show_line(s,sp->p[1].x,sp->p[1].y,sp->p[3].x,sp->p[3].y,col);
		}
	}

	if (s->havefids) {
		for (i = 0; i < 4; i++) {
			unsigned long col = 0x0000ff;	/* Red */
			double xx1 = s->fid[i * 2 + 0];
			double yy1 = s->fid[i * 2 + 1];
			double x1,y1,x2,y2, x3,y3,x4,y4;
			double xsz, ysz;


			/* Make corner point the right way */
			if (i == 0) {
				xsz = s->fidsize;
				ysz = s->fidsize;
			} else if (i == 1) {
				xsz = -s->fidsize;
				ysz = s->fidsize;
			} else if (i == 2) {
				xsz = -s->fidsize;
				ysz = -s->fidsize;
			} else {
				xsz = s->fidsize;
				ysz = -s->fidsize;
			}
	
			/* Create an aligned corner at the fiducial point */
			ptrans(&x1, &y1, xx1, yy1, s->ptrans);
			ptrans(&x2, &y2, xx1 + xsz, yy1, s->ptrans);
			ptrans(&x3, &y3, xx1, yy1, s->ptrans);
			ptrans(&x4, &y4, xx1, yy1 + ysz, s->ptrans);

			ev |= show_line(s,(int)(x1+0.5),(int)(y1+0.5),(int)(x2+0.5),(int)(y2+0.5),col);
			ev |= show_line(s,(int)(x3+0.5),(int)(y3+0.5),(int)(x4+0.5),(int)(y4+0.5),col);
		}
	}

	return ev;
}

/********************************************************************************/
/* Add groups to diagnostic output image */

#undef DBG
#define DBG(aaa) fprintf aaa, fflush(dbgo)

static int
show_groups(
scanrd_ *s
) {
	int stride = 3 * s->width;
	unsigned char *base = s->out; 
	points *tp;
	int x,i,k = 0;
	static unsigned char cc[3 * 24] = {	/* Group palet */
		0x00,0xff,0xff,
		0x00,0x80,0x00,
		0xff,0x00,0xff,
		0x00,0x80,0x80,
		0x00,0xff,0x00,
		0x00,0x80,0xff,
		0x00,0x00,0x80,
		0x80,0xff,0x00,
		0x00,0xff,0x80,
		0xff,0x80,0x00,
		0x00,0x00,0xff,
		0xff,0x80,0x80,
		0x80,0x80,0x00,
		0xff,0xff,0x00,
		0x80,0x80,0x80,
		0x80,0xff,0x80,
		0xff,0xff,0x80,
		0x80,0xff,0xff,
		0xff,0x00,0x80,
		0x80,0x00,0xff,
		0x80,0x80,0xff,
		0xff,0x80,0xff,
		0x80,0x00,0x80,
		0xff,0xff,0xff
		};


	i = 0;
	tp = s->gdone;
	FOR_ALL_ITEMS(points, tp)
		int j;
		/* DBG((dbgo,"Done %d has %d runs\n",i,tp->no)); */
		for (j = 0; j < tp->no; j++) {
			int idx = tp->r[j].y * stride;
			/* Expand the run */
			for (x = tp->r[j].lx; x < tp->r[j].hx; x++) {
				int iidx = idx + 3 * x;
				base[iidx] = cc[k];
				base[iidx+1] = cc[k+1];
				base[iidx+2] = cc[k+2];
			}
		}
		k += 3;
		if (k == (24 * 3))
			k = 0;
		i++;
	END_FOR_ALL_ITEMS(tp);

	return 0;
}
/********************************************************************************/
#ifndef AA_LINES
/* Draw a line in the output diagnostic raster */
static int
show_line(
scanrd_ *s,							/* scanrd object */
int x1, int y1, int x2, int y2,		/* line start and end points */
unsigned long c						/* Color */
) {
	unsigned char *base;				/* Raster base of line */
	int pitch = 3 * s->width;			/* Pitch of raster in pixels */
	int ow = s->width, oh = s->height;	/* width and height of raster for clipping */
	int dx, dy;			/* Line deltas */
	int adx, ady;		/* Absolute deltas */

	int e, k1, k2;		/* Error and axial/diagonal error change values */
	int m1,m2;		/* axial/diagonal coordinate change values */

	int ll;				/* Line length */

	/* Do a crude clip */
	if (x1 < 0)
		x1 = 0;
	if (x1 >= ow)
		x1 = ow-1;
	if (x2 < 0)
		x2 = 0;
	if (x2 >= ow)
		x2 = ow-1;
	if (y1 < 0)
		y1 = 0;
	if (y1 >= oh)
		y1 = oh-1;
	if (y2 < 0)
		y2 = 0;
	if (y2 >= oh)
		y2 = oh-1;

	/* calculate the standard constants */
	dx = x2 - x1;
	dy = y2 - y1;

	if(dx  < 0) {
		m1 = -3;		/* x is going backwards */
		adx = -dx;		/* make this absolute */
	} else {
		m1 = 3;			/* x is going forwards */
		adx = dx;
	}
	
	e = 0;
	if(dy < 0) {
		m2 = -pitch;	/* y is going upwards (decreasing) */
		ady = -dy;		/* make this absolute */
		e = -1;			/* make lines retraceable */
	} else {
		m2 = pitch;		/* y is going downwards (increasing) */
		ady = dy;
	}

	/* m1 has been set to x increment, m2 to y increment */

	m2 += m1;			/* make m2 the diagonal address increment */
						/* and m1 the x axial inrement */
	if(adx > ady) {			/* x is driven */
		ll = adx;
		k1 = 2 * ady;
		k2 = 2 * (ady - adx);
		e += k1 - adx;
	} else {
		ll = ady;
		k1 = 2 * adx;
		k2 = 2 * (adx - ady);
		e += k1 - ady;
		m1 = m2 - m1;		/* Make m1 the y increment */
	}

	/* Start pixel of line */
	base = s->out + y1 * pitch + 3 * x1;

	ll++;	/* Draw start and end point */

	while( ll > 0) {
		while(e < 0 && ll > 0) {
			base[0] = c;
			base[1] = c >> 8;
			base[2] = c >> 16;
			base += m1;
			e += k1;
			ll--;
		}
		while(e >= 0 && ll > 0) {
			base[0] = c;
			base[1] = c >> 8;
			base[2] = c >> 16;
			base += m2;
			e += k2;
			ll--;
		}
	}
	return 0;
}
#else /* AA_LINES: Use anti aliased line drawer */

/*  
    AUTHOR:  Kelvin Thompson

    DESCRIPTION:  Code to render an anti-aliased line, from
      "Rendering Anti-Aliased Lines" in _Graphics_Gems_.

      This is derived from the code printed on pages 690-693
      of _Graphics_Gems_.  An overview of the code is on pages
      105-106.
*/

/* macros to access the frame buffer */
#define PIXINC(dx,dy)  ((dy) * pitch + 3 * (dx))
#define PIXADDR(xx,yy) (s->out + PIXINC(xx,yy))

/* fixed-point data types and macros */
typedef int FX;
#define FX_FRACBITS 16  /* bits of fraction in FX format */
#define FX_0        0   /* zero in fixed-point format */
#define FLOAT_TO_FX(flt)  ((FX)((flt)*(1<<FX_FRACBITS)+0.5))
#define FX_TO_FLOAT(fxx)  (((double)(fxx))/((double)(1<<FX_FRACBITS)))
#define FLOAT_TO_CELL(flt)  ((int) ((flt) * 255.0 + 0.5))
#define MAXVAL_CELL         255
#define COVERAGE(fxval) (s->coverage[(fxval) >> s->covershift])

/* Other aa macros */
#define SWAP(a,b)	((a)^=(b), (b)^=(a), (a)^=(b))

/* BLENDING FUNCTION: */
/*  'cover' is coverage -- in the range [0,255] */
/*  'back' is background color -- in the range [0,255] */
/*  'fgnd' is foreground color -- in the range [0,255] */
#define BLEND(cover,fgnd,back) ( \
	(							\
	  ((255-(cover)) * (back))	\
	+ (     (cover)  * (fgnd))	\
	) >> 8						\
)

/* LINE DIRECTION bits and tables */
#define DIR_STEEP  1  /* set when abs(dy) > abs(dx) */
#define DIR_NEGY   2  /* set whey dy < 0 */

/* --------------------- */
int Anti_Init (scanrd_ *s) {
	float line_r;
	float pix_r;
	int covercells;
	int *thiscell;
	double maxdist,nowdist,incdist;
	int tablebits,radbits;
	int tablecells;
	static int tablesize=0;
	double fnear,ffar,fcover;
	double half,invR,invpiRsq,invpi,Rsq;
	double sum_r;
	double inv_log_2;
	int pitch;

	/* init */
	s->coverage = NULL;

	line_r     = 0.717f;	 /* line radius */
	pix_r      = 0.5;	 	/* pixel radius */
	covercells = 128;

	inv_log_2  = 1.0 / log( 2.0 );
	sum_r      = line_r + pix_r;
	tablebits  = (int) ( log((double)covercells) * inv_log_2 + 0.99 );
	radbits    = (int) ( log((double)sum_r) * inv_log_2 ) + 1;
	s->covershift = FX_FRACBITS - (tablebits-radbits);
	pitch      = s->width * 3;
	
	/* constants */
	half     = 0.5;
	invR     = 1.0 / pix_r;
	invpi    = 1.0 / M_PI;
	invpiRsq = invpi * invR * invR;
	Rsq      = pix_r * pix_r;
#define FRACCOVER(d) (half - d*sqrt(Rsq-d*d)*invpiRsq - invpi*asin(d*invR))
	
	/* pixel increment values  */
	s->adj_pixinc[0] = PIXINC(1,0);
	s->adj_pixinc[1] = PIXINC(0,1);
	s->adj_pixinc[2] = PIXINC(1,0);
	s->adj_pixinc[3] = PIXINC(0,-1);

	s->diag_pixinc[0] = PIXINC(1,1);
	s->diag_pixinc[1] = PIXINC(1,1);
	s->diag_pixinc[2] = PIXINC(1,-1);
	s->diag_pixinc[3] = PIXINC(1,-1);

	s->orth_pixinc[0] = PIXINC(0,1);
	s->orth_pixinc[1] = PIXINC(1,0);
	s->orth_pixinc[2] = PIXINC(0,-1);
	s->orth_pixinc[3] = PIXINC(1,0);

	/* allocate table */
	s->Pmax = FLOAT_TO_FX(sum_r);
	s->Pmax >>= s->covershift;
	tablecells = s->Pmax + 2;
	s->Pmax <<= s->covershift;
	
	if ((s->coverage = (FX *) malloc( tablecells * sizeof(int))) == NULL) {
		s->errv = SI_MALLOC_AAINIT;
		sprintf(s->errm,"aa_line init: Failed to malloc internal table");
		return 1;
	}
	tablesize = tablecells;
	
	/* init for fill loops */
	nowdist = 0.0;
	thiscell = s->coverage;
	incdist = sum_r / (double)(tablecells-2);
	
	/* fill fat portion */
	if (pix_r <= line_r) {
		maxdist = line_r - pix_r;
		for (;nowdist <= maxdist; nowdist += incdist,  ++thiscell)
			*thiscell = MAXVAL_CELL;
	} else { /* fill skinny portion */

		/* loop till edge of line, or end of skinny, whichever comes first */
		maxdist = pix_r - line_r;
		if (maxdist > line_r)
			maxdist = line_r;
		for (;nowdist < maxdist;nowdist += incdist,  ++thiscell) {
			fnear = line_r - nowdist;
			ffar = line_r + nowdist;
			fcover = 1.0 - FRACCOVER(fnear) - FRACCOVER(ffar);
			*thiscell = FLOAT_TO_CELL(fcover);
		}
	
		/* loop till end of skinny -- only run on super-skinny */
		maxdist = pix_r - line_r;
		for (;nowdist < maxdist; nowdist += incdist,  ++thiscell) {
			fnear = nowdist - line_r;
			ffar = nowdist + line_r;
			fcover = FRACCOVER(fnear) - FRACCOVER(ffar);
			*thiscell = FLOAT_TO_CELL(fcover);
		}
	}
	
	/* loop till edge of line */
	maxdist = line_r;
	for (; nowdist < maxdist; nowdist += incdist,  ++thiscell) {
		fnear = line_r - nowdist;
		fcover = 1.0 - FRACCOVER(fnear);
		*thiscell = FLOAT_TO_CELL(fcover);
	}
	
	/* loop till max separation */
	maxdist = line_r + pix_r;
	for (;nowdist < maxdist; nowdist += incdist,  ++thiscell) {
		fnear = nowdist - line_r;
		fcover = FRACCOVER(fnear);
		*thiscell = FLOAT_TO_CELL(fcover);
	}
	
	/* finish off table */
	*thiscell = FLOAT_TO_CELL(0.0);
	s->coverage[tablecells-1] = FLOAT_TO_CELL(0.0);

	s->aa_inited = 1;
	return 0;
#undef FRACCOVER
}

/* --------------------------------------------------------- */
/* Draw an anti-aliased line in the output diagnostic raster */
static int
show_line(
scanrd_ *s,							/* scanrd object */
int X1, int Y1, int X2, int Y2,		/* line start and end points */
unsigned long c						/* Color */
) {
	int 	Bvar, 	/* decision variable for Bresenham's */
	    	Bainc,   /* adjacent-increment for 'Bvar' */
	    	Bdinc;   /* diagonal-increment for 'Bvar' */
	FX 		Pmid,  	/* perp distance at Bresenham's pixel */
	   		Pnow,  	/* perp distance at current pixel (ortho loop) */
	   		Painc, 	/* adjacent-increment for 'Pmid' */
	   		Pdinc, 	/* diagonal-increment for 'Pmid' */
	   		Poinc; 	/* orthogonal-increment for 'Pnow'--also equals 'k' */
	double	fPoinc;	/* Float version of Poinc */
	unsigned char *mid_addr,   /* pixel address for Bresenham's pixel */
	     	      *now_addr;   /* pixel address for current pixel */
	int 	addr_ainc,   /* adjacent pixel address offset */
	    	addr_dinc,   /* diagonal pixel address offset */
	    	addr_oinc;   /* orthogonal pixel address offset */
	int dx,dy,dir;    	/* direction and deltas */
	double fslope;		/* slope of line */
	int pitch     = s->width * 3;
	int ow = s->width, oh = s->height;	/* width and height of raster for clipping */
	int c0,c1,c2;		/* Pixel values */

	if (s->aa_inited == 0) {
		if (Anti_Init(s))
			return 1;	/* Error */
	}

	c0 = c & 0xff;
	c1 = (c >> 8) & 0xff;
	c2 = (c >> 16) & 0xff;

	/* Do a crude clip */
	if (X1 < 1)
		X1 = 1;
	if (X1 >= ow-1)
		X1 = ow-2;
	if (X2 < 1)
		X2 = 1;
	if (X2 >= ow-1)
		X2 = ow-2;
	if (Y1 < 1)
		Y1 = 1;
	if (Y1 >= oh-1)
		Y1 = oh-2;
	if (Y2 < 1)
		Y2 = 1;
	if (Y2 >= oh-1)
		Y2 = oh-2;


	/* rearrange ordering to force left-to-right */
	if 	( X1 > X2 )
	  	{ SWAP(X1,X2);  SWAP(Y1,Y2); }
	
	/* init deltas */
	dx = X2 - X1;  /* guaranteed non-negative */
	dy = Y2 - Y1;

	/* Sanity check */
	if (dx == 0.0 && dy == 0.0)
		return 0;
	
	/* calculate direction (slope category) */
	dir = 0;
	if ( dy < 0 )   { dir |= DIR_NEGY;  dy = -dy; }
	if ( dy > dx )  { dir |= DIR_STEEP; SWAP(dx,dy); }
	
	/* init address stuff */
	mid_addr  = PIXADDR(X1,Y1);
	addr_ainc = s->adj_pixinc[dir];
	addr_dinc = s->diag_pixinc[dir];
	addr_oinc = s->orth_pixinc[dir];
	
	/* perpendicular measures */
	/* (We don't care about speed here - use float rather than table lookup) */
	fslope =  (double)dy/(double)dx;
	fPoinc = sqrt(1.0/(1.0 + (fslope * fslope)));
	Poinc = FLOAT_TO_FX(fPoinc);
	Painc = FLOAT_TO_FX(fPoinc * fslope);
	Pdinc = Painc - Poinc;
	Pmid  = FX_0;
	
	/* init Bresenham's */
	Bainc = dy << 1;
	Bdinc = (dy-dx) << 1;
	Bvar = Bainc - dx;
	
	do	{
		int cvg;

	  	/* do middle pixel */
	  	cvg = COVERAGE(abs(Pmid));
	  	mid_addr[0] = BLEND(cvg, c0, mid_addr[0]);
	  	mid_addr[1] = BLEND(cvg, c1, mid_addr[1]);
	  	mid_addr[2] = BLEND(cvg, c2, mid_addr[2]);
	
	  	/* go up orthogonally */
	  	for (
	      	Pnow = Poinc - Pmid,  now_addr = mid_addr + addr_oinc;
	      	Pnow < s->Pmax;
	      	Pnow += Poinc,      now_addr += addr_oinc
	      	) {
	  		cvg = COVERAGE(Pnow);
	  		now_addr[0] = BLEND(cvg, c0, now_addr[0]);
	  		now_addr[1] = BLEND(cvg, c1, now_addr[1]);
	  		now_addr[2] = BLEND(cvg, c2, now_addr[2]);
		}
	
	  	/* go down orthogonally */
	  	for (Pnow = Poinc + Pmid,  now_addr = mid_addr - addr_oinc;
	      	 Pnow < s->Pmax;
	      	 Pnow += Poinc,      now_addr -= addr_oinc
	      	) {
	  		cvg = COVERAGE(Pnow);
	  		now_addr[0] = BLEND(cvg, c0, now_addr[0]);
	  		now_addr[1] = BLEND(cvg, c1, now_addr[1]);
	  		now_addr[2] = BLEND(cvg, c2, now_addr[2]);
		}
	
	  	/* update Bresenham's */
	  	if ( Bvar < 0 ) {
	    	Bvar += Bainc;
	    	mid_addr += addr_ainc;
	    	Pmid += Painc;
    	} else {
	    	Bvar += Bdinc;
	    	mid_addr += addr_dinc;
	    	Pmid += Pdinc;
    	}
	
	  	--dx;
	} while (dx >= 0);
	return 0;
}

#undef PIXINC
#undef PIXADDR
#undef FX_FRACBITS
#undef FX_0
#undef FLOAT_TO_FX
#undef FX_TO_FLOAT
#undef FLOAT_TO_CELL
#undef MAXVAL_CELL
#undef COVERAGE
#undef SWAP
#undef BLEND
#undef DIR_STEEP
#undef DIR_NEGY

#endif /* !AA_LINES */

/********************************************************************************/
/* Diagnostic vector text output routines */

/* 16 segment ASCII from 0x20 to 0x5f */
/*  
           0      1
         ------ ------
        |\10   11    /|
      7 |  \   |  12  | 2
        |    \ |/     |
         --8--- ---9--
        |     /|\     |
      6 |  15  |  13  | 3
        | /    14   \ |
         ------ ------
            5     4
 */

unsigned short vfont[64] = 
	{
	0x0000, 0x0820, 0x0880, 0x4b3c, 0x4bbb, 0xdb99, 0x2d79, 0x1000, /*  !"#$%&' */
	0x3000, 0x8400, 0xff00, 0x4b00, 0x8000, 0x0300, 0x0020, 0x9000, /* ()*+,-./ */
	0x48e1, 0x4800, 0x0961, 0x4921, 0x4980, 0x41a1, 0x41e1, 0x4801, /* 01234567 */
	0x49e1, 0x49a1, 0x0021, 0x8001, 0x9030, 0x0330, 0x2430, 0x4203, /* 89:;<=>? */
	0x417f, 0x03cf, 0x4a3f, 0x00f3, 0x483f, 0x03f3, 0x01c3, 0x02fb, /* @ABCDEFG */
	0x03cc, 0x4833, 0x4863, 0x31c0, 0x00f0, 0x14cc, 0x24cc, 0x00ff, /* HIJKLMNO */
	0x03c7, 0x20ff, 0x23c7, 0x03bb, 0x4803, 0x00fc, 0x90c0, 0xa0cc, /* PQRSTUVW */
	0xb400, 0x5400, 0x9033, 0x00e1, 0x2400, 0x001e, 0xa000, 0x0030  /* XYZ[\]^_ */
	};

static int show_char(scanrd_ *s, char c,	double x, double y,
	double sc, unsigned long col);

/* Print a string to the diagnostic raster with ptrans() */
/* Return non-zero on error */
static int
show_string(
scanrd_ *s,			/* scanrd object */
char *is,			/* Input string */
double x, double y,	/* Center point for string */
double w,			/* Width total for string */
unsigned long col	/* Color value */
) {
	int i,n;
	double uw;	/* String unscaled width */
	double sc;	/* Scale factor */

	if (w < 0.0)
		w = -w;
	n = strlen(is);
	if (n == 0)
		return 0;
	
	/* Total unscaled width of the string */
	uw = (n * 0.8 + (n >= 1 ? (n-1) * 0.3 : 0));
	/* Compute string scale factor */
	sc = w/uw;

	/* adjust starting point for first char */
	x -= sc * uw/2.0;
	y -= sc * 0.5;

	for (i = 0; i < n; i++) {
		if (show_char(s,is[i],x,y,sc,col))
			return 1;
		x += sc * (0.8 + 0.3);
	}
	return 0;
}

static void show_xfm_line(scanrd_ *s, double x1, double y1, double x2, double y2,
	unsigned long col);

/* Write a character to the diagnostic raster with ptrans() */
/* Return non-zero on error */
static int
show_char(
scanrd_ *s,			/* scanrd object */
char c,				/* Input character */
double x, double y,	/* Top left point of character */
double sc,			/* Scale factor */
unsigned long col
) {
	int ci;
	unsigned int cd;

	ci = c - 0x20;
	if (ci < 0 || ci > 0x3f)
		ci = '?' - 0x20;
	cd = vfont[ci];
	/* Display each segment */
	if (cd & 0x0001)
		show_xfm_line(s, x,y,x+sc*0.4,y,col);
	if (cd & 0x0002)
		show_xfm_line(s, x+sc*0.4,y,x+sc*0.8,y,col);
	if (cd & 0x0004)
		show_xfm_line(s, x+sc*0.8,y,x+sc*0.8,y+sc*0.5,col);
	if (cd & 0x0008)
		show_xfm_line(s, x+sc*0.8,y+sc*0.5,x+sc*0.8,y+sc*1.0,col);
	if (cd & 0x0010)
		show_xfm_line(s, x+sc*0.8,y+sc*1.0,x+sc*0.4,y+sc*1.0,col);
	if (cd & 0x0020)
		show_xfm_line(s, x+sc*0.4,y+sc*1.0,x+0.0,y+sc*1.0,col);
	if (cd & 0x0040)
		show_xfm_line(s, x+0.0,y+sc*1.0,x+0.0,y+sc*0.5,col);
	if (cd & 0x0080)
		show_xfm_line(s, x+0.0,y+sc*0.5,x+0.0,y+0.0,col);
	if (cd & 0x0100)
		show_xfm_line(s, x+0.0,y+sc*0.5,x+sc*0.4,y+sc*0.5,col);
	if (cd & 0x0200)
		show_xfm_line(s, x+sc*0.4,y+sc*0.5,x+sc*0.8,y+sc*0.5,col);
	if (cd & 0x0400)
		show_xfm_line(s, x+0.0,y+0.0,x+sc*0.4,y+sc*0.5,col);
	if (cd & 0x0800)
		show_xfm_line(s, x+sc*0.4,y+0.0,x+sc*0.4,y+sc*0.5,col);
	if (cd & 0x1000)
		show_xfm_line(s, x+sc*0.8,y+0.0,x+sc*0.4,y+sc*0.5,col);
	if (cd & 0x2000)
		show_xfm_line(s, x+sc*0.8,y+sc*1.0,x+sc*0.4,y+sc*0.5,col);
	if (cd & 0x4000)
		show_xfm_line(s, x+sc*0.4,y+sc*1.0,x+sc*0.4,y+sc*0.5,col);
	if (cd & 0x8000)
		show_xfm_line(s, x+0.0,y+sc*1.0,x+sc*0.4,y+sc*0.5,col);
	return 0;
}

/* Write transformed line to the diagnostic raster with ptrans() */
static void
show_xfm_line(
scanrd_ *s,
double x1, double y1, double x2, double y2,
unsigned long col
) {
	double xx1,yy1,xx2,yy2;

	ptrans(&xx1, &yy1, x1, y1, s->ptrans);
	ptrans(&xx2, &yy2, x2, y2, s->ptrans);

	show_line(s,(int)(xx1+0.5),(int)(yy1+0.5),(int)(xx2+0.5),(int)(yy2+0.5),col);
}

/********************************************************************************/
/* Transform from the input raster colorspace to the diagnostic raster space */
static void toRGB(
unsigned char *dst,
unsigned char *src,
int depth, int bpp
) {
	if (bpp == 8) {
		if (depth == 3) {
			dst[0] = src[0];		/* Transfer input to output */
			dst[1] = src[1];
			dst[2] = src[2];
		} else if (depth == 4) {	/* Do a crude conversion */
			double cmyk[4];
			int e;
			for (e = 0; e < 4; e++)
				cmyk[e] = src[e]/255.0;
			for (e = 0; e < 3; e++) {
				cmyk[e] = cmyk[e] * 0.7 + 0.3 * cmyk[3];
				if (cmyk[e] < cmyk[3])
					cmyk[e] = cmyk[3];
				dst[e] = 255 - (int)(cmyk[e] * 255.0 + 0.5);
			}
		} else {	/* Hmm */
			dst[0] = 
			dst[1] = 
			dst[2] = src[0];
		}
	} else {
		unsigned short *src2 = (unsigned short *)src;

		if (depth == 3) {
			dst[0] = src2[0]/257;	/* Transfer input to output */
			dst[1] = src2[1]/257;	/* with 16 to 8bpp conversion */
			dst[2] = src2[2]/257;
		} else if (depth == 4) {	/* Do a crude conversion */
			double cmyk[4];
			int e;
			for (e = 0; e < 4; e++)
				cmyk[e] = src2[e]/65535.0;
			for (e = 0; e < 3; e++) {
				cmyk[e] = cmyk[e] * 0.7 + 0.3 * cmyk[3];
				if (cmyk[e] < cmyk[3])
					cmyk[e] = cmyk[3];
				dst[e] = 255 - (int)(cmyk[e] * 255.0 + 0.5);
			}
		} else {	/* Hmm */
			dst[0] = 
			dst[1] = 
			dst[2] = src2[0]/257;
		}
	}
}


/* Convert from XYZ scale 100 to Lab D50 */
static void XYZ2Lab(double *out, double *in) {
	double X = in[0], Y = in[1], Z = in[2];
	double x,y,z,fx,fy,fz;

	x = X/96.42;
	y = Y/100.0;
	z = Z/82.49;

	if (x > 0.008856451586)
		fx = pow(x,1.0/3.0);
	else
		fx = 7.787036979 * x + 16.0/116.0;

	if (y > 0.008856451586)
		fy = pow(y,1.0/3.0);
	else
		fy = 7.787036979 * y + 16.0/116.0;

	if (z > 0.008856451586)
		fz = pow(z,1.0/3.0);
	else
		fz = 7.787036979 * z + 16.0/116.0;

	out[0] = 116.0 * fy - 16.0;
	out[1] = 500.0 * (fx - fy);
	out[2] = 200.0 * (fy - fz);
}

/* Convert from a scanned pixel value to an aproximate Lab value */
static void pval2Lab(double *out, double *in, int depth) {
	double wXYZ[3];
	double XYZ[3];
	int e, j;

	if (depth == 3) {	/* Assume RGB */

		double clrnts[3][3] = {	/* Red, Green & Blue XYZ values */
			{ 0.412414, 0.212642, 0.019325 },
			{ 0.357618, 0.715136, 0.119207 },
			{ 0.180511, 0.072193, 0.950770 }
		};

		wXYZ[0] = 0.950543;		/* Because we're using sRGB primaries */
		wXYZ[1] = 1.0;			/* the white point is D65 */
		wXYZ[2] = 1.089303;

		XYZ[0] = XYZ[1] = XYZ[2] = 0.0;

		for (e = 0; e < 3; e++) {
			double v = in[e]/255.0;
				
			if (v < 0.0)
				v = 0.0;
			else if (v > 1.0)
				v = 1.0;
			if (v <= 0.03928)
				v /= 12.92;
			else
				v = pow((0.055 + v)/1.055, 2.4);		/* Gamma */

			for (j = 0; j < 3; j++)			/* Sum colorant XYZ */
				XYZ[j] += v * clrnts[e][j];
		}

	} else {
		/* We assume a simple screened subtractive filter model, with dot gain */

		double clrnts[4][3] = {	/* CMYK XYZ values */
			{ 0.12, 0.18, 0.48 },
			{ 0.38, 0.19, 0.20 },	
			{ 0.76, 0.81, 0.11 },	
			{ 0.04, 0.04, 0.04 }
		};

		/* start with white */
		XYZ[0] = wXYZ[0] = 0.9642;
		XYZ[1] = wXYZ[1] = 1.0;
		XYZ[2] = wXYZ[2] = 0.8249;

		/* And filter it out for each component */
		for (e = 0; e < 4; e++) {
			double v = in[e]/255.0;
				
			if (v < 0.0)
				v = 0.0;
			else if (v > 1.0)
				v = 1.0;
			v = 1.0 - pow(1.0 - v, 2.2); /* Compute dot gain */

			for (j = 0; j < 3; j++) {
				double fv;

				/* Normalise filtering effect of this colorant */
				fv = clrnts[e][j]/wXYZ[j];

				/* Compute screened filtering effect */
				fv = (1.0 - v) + v * fv;

				/* Apply filter to our current value */
				XYZ[j] *= fv;
			}
		}
	}

	/* Convert to Lab */
	{
		double X = XYZ[0], Y = XYZ[1], Z = XYZ[2];
		double x,y,z,fx,fy,fz;

		x = X/wXYZ[0];
		y = Y/wXYZ[1];
		z = Z/wXYZ[2];

		if (x > 0.008856451586)
			fx = pow(x,1.0/3.0);
		else
			fx = 7.787036979 * x + 16.0/116.0;

		if (y > 0.008856451586)
			fy = pow(y,1.0/3.0);
		else
			fy = 7.787036979 * y + 16.0/116.0;

		if (z > 0.008856451586)
			fz = pow(z,1.0/3.0);
		else
			fz = 7.787036979 * z + 16.0/116.0;

		out[0] = 116.0 * fy - 16.0;
		out[1] = 500.0 * (fx - fy);
		out[2] = 200.0 * (fy - fz);
	}
}

/********************************************************************************/

static int
scanrd_write_diag(scanrd_ *s) {
	int y;
	unsigned char *op;
	int stride = 3 * s->width;

	if ((s->flags & SI_SHOW_FLAGS) == 0 || s->write_line == NULL)
		return 0;

	/* Write out the tiff file */
	for (op = s->out, y = 0; y < s->height; ++y, op += stride) {
		if (s->write_line(s->ddata, y, (char *)op)) {
			s->errv = SI_DIAG_WRITE_ERR;
			sprintf(s->errm,"scanrd: write_line() returned error");
			return 1;
		}
	}
	return 0;
}

