
/* Colorimeter Correction Matrix and */
/* Colorimeter Calibration Spectral Sample creation utility */

/* 
 * Argyll Color Management System
 * Author: Graeme W. Gill
 * Date:   19/8/2010
 *
 * Copyright 2010, 2011, 2013 Graeme W. Gill
 * All rights reserved.
 *
 * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
 * see the License.txt file for licencing details.
 */

/* This program uses display measurements from a colorimeter and */
/* a spectrometer to create a correction matrix for a particular */
/* colorimeter/display combination. */
/* It can aslso be used to make display measurements from a spectrometer to create */
/* spectral calibration samples that can be used with a Colorimeter that */
/* knowns its own spectral sensitivity curves (ie. X-Rite i1d3, Spyder 4). */

/* Based on spotread.c, illumread.c, dispcal.c */

/* 
	TTBD:

		Should change -s to generate more neutral colors as well
		as saturated ones.

		Would be nice to have a way of not changing the target
		instruments absolute calibration.

		Would be nice to have a verify option that produces
		a fit report of a matrix vs. the input files.

		Would be nice to have the option of procssing a Spyder 3 correction.txt file.
		(See post from umberto.guidali@tiscali.it)

		Would be nice to be able to use an i1D3 to correct other instruments,
		or an i1D3 created .ti3 as the reference. (can do this with .ti3 files).

		Would be nice to have an option of providing two ICC profiles,
		instead of using .ti3 files (?? How well would it work though ?)
 */


#undef DEBUG

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <time.h>
#include <string.h>
#include <stdarg.h>
#include "copyright.h"
#include "aconfig.h"
#include "numlib.h"
#include "cgats.h"
#include "xspect.h"
#include "insttypes.h"
#include "conv.h"
#include "icoms.h"
#include "inst.h"
#include "ccast.h"
#include "ui.h"
#include "dispwin.h"
#include "webwin.h"
#include "dummywin.h"
#ifdef NT
# include "madvrwin.h"
#endif
#include "dispsup.h"
#include "ccss.h"
#include "ccmx.h"
#include "instappsup.h"
#ifdef ENABLE_USB
# include "spyd2.h"
#endif
#if defined (NT)
#include <conio.h>
#endif

#define DEFAULT_MSTEPS 1
#undef SHOW_WINDOW_ONFAKE	/* Display a test window up for a fake device */
#define COMPORT 1			/* Default com port 1..4 */


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

/* Invoke with -dfake for testing with a fake device. */
/* Invoke with -dFAKE for automatic creation of test matrix. */
/* Will use a fake.icm/.icc profile if present, or a built in fake */
/* device behaviour if not. */

void
/* Flag = 0x0000 = default */
/* Flag & 0x0001 = list ChromCast's */
/* Flag & 0x0002 = list Technology choice */
/* Flag & 0x1xxx = -S flag */
usage(int flag, char *diag, ...) {
	disppath **dp;
	icompaths *icmps = new_icompaths(0);
	inst2_capability cap = 0;

	fprintf(stderr,"Create CCMX or CCSS, Version %s\n",ARGYLL_VERSION_STR);
	fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
	if (diag != NULL) {
		va_list args;
		fprintf(stderr,"Diagnostic: ");
		va_start(args, diag);
		vfprintf(stderr, diag, args);
		va_end(args);
		fprintf(stderr,"\n");
	}
	fprintf(stderr,"usage: ccxxmake -t dtech [-options] output.%s\n", flag & 0x1000 ? "ccss" : "ccmx");
	fprintf(stderr," -v                Verbose mode\n");
	fprintf(stderr," -S                Create CCSS rather than CCMX\n");
	fprintf(stderr," -f ref.ti3[,targ.ti3]  Create from one or two .ti3 files rather than measure.\n");
#if defined(UNIX_X11)
	fprintf(stderr," -display displayname   Choose X11 display name\n");
	fprintf(stderr," -d n[,m]          Choose the display n from the following list (default 1)\n");
	fprintf(stderr,"                   Optionally choose different display m for VideoLUT access\n"); 
#else
	fprintf(stderr," -d n              Choose the display from the following list (default 1)\n");
#endif
	dp = get_displays();
	if (dp == NULL || dp[0] == NULL)
		fprintf(stderr,"    ** No displays found **\n");
	else {
		int i;
		for (i = 0; ; i++) {
			if (dp[i] == NULL)
				break;
			fprintf(stderr,"    %d name = '%s'\n",i+1,dp[i]->name);
			fprintf(stderr,"    %d = '%s'\n",i+1,dp[i]->description);
		}
	}
	free_disppaths(dp);
	fprintf(stderr," -dweb[:port]      Display via a web server at port (default 8080)\n");
	fprintf(stderr," -dcc[:n]          Display via n'th ChromeCast (default 1, ? for list)\n");
	if (flag & 0x001) {
		ccast_id **ids;
		if ((ids = get_ccids()) == NULL) {
			fprintf(stderr,"    ** Error discovering ChromCasts **\n");
		} else {
			if (ids[0] == NULL)
				fprintf(stderr,"    ** No ChromCasts found **\n");
			else {
				int i;
				for (i = 0; ids[i] != NULL; i++)
					fprintf(stderr,"    %d = '%s'\n",i+1,ids[i]->name);
				free_ccids(ids);
			}
		}
	}
#ifdef NT
	fprintf(stderr," -d madvr          Display via MadVR Video Renderer\n");
#endif
	fprintf(stderr," -d dummy          Dummy (non-existant, invisible) display\n");
//	fprintf(stderr," -d fake           Use a fake (ICC profile) display device for testing, fake%s if present\n",ICC_FILE_EXT);
	fprintf(stderr," -p                Use telephoto mode (ie. for a projector, if available)\n");
	fprintf(stderr," -a                Use ambient measurement mode (ie. for a projector, if available)\n");
	cap = inst_show_disptype_options(stderr, " -y c|l                 ", icmps, 1, 0);
	fprintf(stderr," -z disptype       Different display type for spectrometer (see -y)\n");
	fprintf(stderr," -P ho,vo,ss[,vs]  Position test window and scale it\n");
	fprintf(stderr,"                   ho,vi: 0.0 = left/top, 0.5 = center, 1.0 = right/bottom etc.\n");
	fprintf(stderr,"                   ss: 0.5 = half, 1.0 = normal, 2.0 = double etc.\n");
	fprintf(stderr," -F                Fill whole screen with black background\n");
#if defined(UNIX_X11)
	fprintf(stderr," -n                Don't set override redirect on test window\n");
#endif
	fprintf(stderr," -N                Disable initial calibration of instrument if possible\n");
	fprintf(stderr," -H                Use high resolution spectrum mode (if available)\n");
//	fprintf(stderr," -V                Use adaptive measurement mode (if available)\n");
	fprintf(stderr," -C \"command\"      Invoke shell \"command\" each time a color is set\n");
	fprintf(stderr," -M \"command\"      Invoke shell \"command\" each time a color is measured\n");
	fprintf(stderr," -o observ         Choose CIE Observer for CCMX spectrometer data:\n");
	fprintf(stderr,"                    1931_2 (def), 1964_10, 2015_2, 2015_10, S&B 1955_2, shaw, J&V 1978_2 or file.cmf\n");
	fprintf(stderr," -s steps          Override default patch sequence combination steps  (default %d)\n",DEFAULT_MSTEPS);
	fprintf(stderr," -W n|h|x          Override serial port flow control: n = none, h = HW, x = Xon/Xoff\n");
	fprintf(stderr," -D [level]        Print debug diagnostics to stderr\n");
	fprintf(stderr," -E desciption     Override the default overall description\n");
	fprintf(stderr," -I displayname    Set display make and model description (optional)\n");
	if (flag & 0x0002) {
		int i;
		disptech_info *list = disptech_get_list();
		for (i = 0; list[i].dtech != disptech_end; i++)
			fprintf(stderr," %s %s               %s\n",i == 0 ? "-t" : "  ", list[i].lsel,list[i].desc);
	} else {
		fprintf(stderr," -t dtech          Set display technology type\n");
		fprintf(stderr,"                    (Use -?? to list technology choices)\n");
	}
	fprintf(stderr," -U c              Set UI selection character(s)\n");
	fprintf(stderr," -Y r|n            Set or override refresh/non-refresh display type\n");
	fprintf(stderr," -Y R:rate         Override measured refresh rate with rate Hz\n");
	fprintf(stderr," -Y A              Use non-adaptive integration time mode (if available).\n");
	fprintf(stderr," correction.ccmx | calibration.ccss\n");
	fprintf(stderr,"                   File to save result to\n");
	if (icmps != NULL)
		icmps->del(icmps);
	exit(1);
}

typedef double ary3[3];

int main(int argc, char *argv[]) {
	int i,j;
	int fa, nfa, mfa;					/* current argument we're looking at */
	disppath *disp = NULL;				/* Display being used */
	double hpatscale = 1.0, vpatscale = 1.0;	/* scale factor for test patch size */
	double ho = 0.0, vo = 0.0;			/* Test window offsets, -1.0 to 1.0 */
	int fullscreen = 0;            		/* NZ if whole screen should be filled with black */
	int verb = 0;
	int debug = 0;
	int doccss = 0;						/* Create CCSS rather than CCMX */
	int fake = 0;						/* Use the fake device for testing, 2 for auto */
	int faketoggle = 0;					/* Toggle fake between "colorimeter" and "spectro" */
	int fakeseq = 0;					/* Fake auto CCMX sequence */
	int spec = 0;						/* Need spectral data to implement option */
	icxObserverType obType = icxOT_CIE_1931_2;
	xspect custObserver[3];				/* If obType = icxOT_custom */
	int override = 1;					/* Override redirect on X11 */
	icompaths *icmps = NULL;			/* Ports to choose from */
	int comno = COMPORT;				/* COM port used */
	flow_control fc = fc_nc;			/* Default flow control */
	int highres = 0;					/* High res mode if available */
	int ditype = 0;						/* Display kind selector, 0 = default */
	int sditype = -1;					/* Spectro display kind, -1 = use ditype */
	int refrmode = -1;					/* Refresh mode */
	double refrate = 0.0;				/* 0.0 = default, > 0.0 = override refresh rate */ 
	int cbid = 0;						/* Calibration base display mode ID */
	int nadaptive = 0;					/* Use non-adaptive mode if available */
	int tele = 0;						/* NZ if telephoto mode */
	int ambient = 0;					/* NZ if ambient mode */
	int noinitcal = 0;					/* Disable initial calibration */
	double icalmax = 1.0;				/* Scale inst. cal. test values by this (0.0 .. 1.0) */
	int webdisp = 0;					/* NZ for web display, == port number */
	int ccdisp = 0;			 			/* NZ for ChromeCast, == list index */
	ccast_id **ccids = NULL;
	ccast_id *ccid = NULL;
#ifdef NT
	int madvrdisp = 0;					/* NZ for MadVR display */
#endif
	int dummydisp = 0;					/* NZ for dummy display */
	char *ccallout = NULL;				/* Change color Shell callout */
	char *mcallout = NULL;				/* Measure color Shell callout */
	int msteps = DEFAULT_MSTEPS;		/* Patch surface size */
	int npat = 0;						/* Number of patches/colors */
	ary3 *refs = NULL;					/* Reference XYZ values */
	int gotref = 0;
	char *refname = NULL;				/* Name of reference instrument */
	char *reffile = NULL;				/* Name of reference file */
	ary3 *cols = NULL;					/* Colorimeter XYZ values */
	int gotcol = 0;
	char *colname = NULL;				/* Name of colorimeter instrument */
	char *colfile = NULL;				/* Name of colorimeter file */
	col *rdcols = NULL;					/* Internal storage of all the patch colors */
	int saved = 0;						/* Saved result */
	char innames[2][MAXNAMEL+1] = { "\000", "\000" };  /* .ti3 input names */
	char outname[MAXNAMEL+5+1] = "\000";  /* ccmx output file name */
	char *description = NULL;			/* Given overall description */
	char *displayname = NULL;			/* Given display name */
	disptech_info *dtinfo = NULL;		/* Display technology */
	char *uisel = NULL;					/* UI selection letters */
	int uflag = 0;						/* usage flag */
	int rv;


	set_exe_path(argv[0]);				/* Set global exe_path and error_program */
	check_if_not_interactive();

	/* Process the arguments */
	mfa = 0;        /* Minimum final arguments */
	for (fa = 1;fa < argc;fa++) {
		nfa = fa;					/* skip to nfa if next argument is used */
		if (argv[fa][0] == '-') {	/* Look for any flags */
			char *na = NULL;		/* next argument after flag, null if none */

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

			if (argv[fa][1] == '?' || argv[fa][1] == '-') {
				if (argv[fa][2] == '?' || argv[fa][2] == '-')
					usage(uflag | 2, "Extended usage requested");
				usage(uflag | 0, "Usage requested");

			} else if (argv[fa][1] == 'v') {
				verb = 1;
				g_log->verb = verb;

			} else if (argv[fa][1] == 'S') {
				doccss = 1;
				uflag |= 0x1000;

			} else if (argv[fa][1] == 'f') {
				char *cna, *f1 = NULL;
				fa = nfa;
				if (na == NULL) usage(uflag | 0,"Expect argument to input file flag -f");

				if ((cna = strdup(na)) == NULL)
					error("Malloc failed");

				/* If got just one file - enough for CCSS */
				if ((f1 = strchr(cna, ',')) == NULL) {
					strncpy(innames[0],cna,MAXNAMEL-1); innames[0][MAXNAMEL-1] = '\000';
					free(cna);

				/* Got two files - needed for CCMX */
				} else {
					*f1++ = '\000';
					strncpy(innames[0],cna,MAXNAMEL-1); innames[0][MAXNAMEL-1] = '\000';
					strncpy(innames[1],f1,MAXNAMEL-1); innames[1][MAXNAMEL-1] = '\000';
					free(cna);
				}

			/* Display number */
			} else if (argv[fa][1] == 'd') {
				if (strncmp(na,"web",3) == 0
				 || strncmp(na,"WEB",3) == 0) {
					webdisp = 8080;
					if (na[3] == ':') {
						webdisp = atoi(na+4);
						if (webdisp == 0 || webdisp > 65535)
							usage(uflag | 0,"Web port number must be in range 1..65535");
					}
					fa = nfa;
				} else if (strncmp(na,"cc",2) == 0
				 || strncmp(na,"CC",2) == 0) {
					ccdisp = 1;
					if (na[2] == ':') {
						if (na[3] < '0' || na[3] > '9')
							usage(uflag | 0x0001,"Available ChromeCasts");

						ccdisp = atoi(na+3);
						if (ccdisp <= 0)
							usage(uflag | 0,"ChromCast number must be in range 1..N");
					}
					fa = nfa;
#ifdef NT
				} else if (strncmp(na,"madvr",5) == 0
				 || strncmp(na,"MADVR",5) == 0) {
					madvrdisp = 1;
					fa = nfa;
#endif
				} else if (strncmp(na,"dummy",5) == 0
				 || strncmp(na,"DUMMY",5) == 0) {
					dummydisp = 1;
					fa = nfa;
				} else {
#if defined(UNIX_X11)
					int ix, iv;

					if (strcmp(&argv[fa][2], "isplay") == 0 || strcmp(&argv[fa][2], "ISPLAY") == 0) {
						if (++fa >= argc || argv[fa][0] == '-') usage(uflag | 0,"Parameter expected following -display");
						setenv("DISPLAY", argv[fa], 1);
					} else {
						if (na == NULL) usage(uflag | 0,"Parameter expected following -d");
						fa = nfa;
						if (strcmp(na,"fake") == 0 || strcmp(na,"FAKE") == 0) {
							fake = 1;
							if (strcmp(na,"FAKE") == 0)
								fakeseq = 1;
						} else {
							if (sscanf(na, "%d,%d",&ix,&iv) != 2) {
								ix = atoi(na);
								iv = 0;
							}
							if (disp != NULL)
								free_a_disppath(disp);
							if ((disp = get_a_display(ix-1)) == NULL)
								usage(uflag | 0,"-d parameter %d out of range",ix);
							if (iv > 0)
								disp->rscreen = iv-1;
						}
					}
#else
					int ix;
					if (na == NULL) usage(uflag | 0,"Parameter expected following -d");
					fa = nfa;
					if (strcmp(na,"fake") == 0 || strcmp(na,"FAKE") == 0) {
						fake = 1;
						if (strcmp(na,"FAKE") == 0)
							fakeseq = 1;
					} else {
						ix = atoi(na);
						if (disp != NULL)
							free_a_disppath(disp);
						if ((disp = get_a_display(ix-1)) == NULL)
							usage(uflag | 0,"-d parameter %d out of range",ix);
					}
#endif
				}
#if defined(UNIX_X11)
			} else if (argv[fa][1] == 'n') {
				override = 0;
#endif /* UNIX */

			/* COM port  */
			} else if (argv[fa][1] == 'c') {
				fa = nfa;
				if (na == NULL) usage(uflag | 0,"Parameter expected following -c");
				comno = atoi(na);
				if (comno < 1 || comno > 40) usage(uflag | 0,"-c parameter %d out of range",comno);

			/* Telephoto */
			} else if (argv[fa][1] == 'p') {
				tele = 1;
				ambient = 0;

			/* Ambient */
			} else if (argv[fa][1] == 'a') {
				ambient = 1;
				tele = 0;

			/* Display type */
			} else if (argv[fa][1] == 'y') {
				fa = nfa;
				if (na == NULL) usage(uflag | 0,"Parameter expected after -y");
				ditype = na[0];
				if (ditype == '_' && na[1] != '\000')
					ditype = ditype << 8 | na[1];

				/* For ccss, set a default if given */
				if (na[0] == 'r') {
					refrmode = 1;
				} else if (na[0] == 'n') {
					refrmode = 0;
				}

			/* Spectro display type */
			} else if (argv[fa][1] == 'z') {
				fa = nfa;
				if (na == NULL) usage(uflag | 0,"Parameter expected after -z");
				sditype = na[0];

			/* Test patch offset and size */
			} else if (argv[fa][1] == 'P') {
				fa = nfa;
				if (na == NULL) usage(uflag | 0,"Parameter expected after -P");
				if (sscanf(na, " %lf,%lf,%lf,%lf ", &ho, &vo, &hpatscale, &vpatscale) == 4) {
					;
				} else if (sscanf(na, " %lf,%lf,%lf ", &ho, &vo, &hpatscale) == 3) {
					vpatscale = hpatscale;
				} else {
					usage(uflag | 0,"-P parameter '%s' not recognised",na);
				}
				if (ho < 0.0 || ho > 1.0
				 || vo < 0.0 || vo > 1.0
				 || hpatscale <= 0.0 || hpatscale > 50.0
				 || vpatscale <= 0.0 || vpatscale > 50.0)
					usage(uflag | 0,"-P parameters %f %f %f %f out of range",ho,vo,hpatscale,vpatscale);
				ho = 2.0 * ho - 1.0;
				vo = 2.0 * vo - 1.0;

			/* Full screen black background */
			} else if (argv[fa][1] == 'F') {
				fullscreen = 1;

			/* No initial calibration */
			} else if (argv[fa][1] == 'N') {
				noinitcal = 1;

			/* High res spectral mode */
			} else if (argv[fa][1] == 'H') {
				highres = 1;

			/* Adaptive mode - now default, so flag is deprecated */
			} else if (argv[fa][1] == 'V') {
				warning("dispcal -V flag is deprecated");

			/* Spectral Observer type (only relevant for CCMX) */
			} else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') {
				fa = nfa;
				if (na == NULL) usage(uflag | 0,"Parameter expecte after -o");
				if (strcmp(na, "1931_2") == 0) {			/* Classic 2 degree */
					spec = 2;
					obType = icxOT_CIE_1931_2;
				} else if (strcmp(na, "1964_10") == 0) {	/* Classic 10 degree */
					spec = 2;
					obType = icxOT_CIE_1964_10;
				} else if (strcmp(na, "2015_2") == 0) {		/* Latest 2 degree */
					spec = 2;
					obType = icxOT_CIE_2015_2;
				} else if (strcmp(na, "2015_10") == 0) {	/* Latest 10 degree */
					spec = 2;
					obType = icxOT_CIE_2015_10;
				} else if (strcmp(na, "1955_2") == 0) {		/* Stiles and Burch 1955 2 degree */
					spec = 2;
					obType = icxOT_Stiles_Burch_2;
				} else if (strcmp(na, "1978_2") == 0) {		/* Judd and Voss 1978 2 degree */
					spec = 2;
					obType = icxOT_Judd_Voss_2;
				} else if (strcmp(na, "shaw") == 0) {		/* Shaw and Fairchilds 1997 2 degree */
					spec = 2;
					obType = icxOT_Shaw_Fairchild_2;
				} else {	/* Assume it's a filename */
					obType = icxOT_custom;
					if (read_cmf(custObserver, na) != 0)
						usage(uflag | 0,"Failed to read custom observer CMF from -o file '%s'",na);
				}

			} else if (argv[fa][1] == 's') {
				fa = nfa;
				if (na == NULL) usage(uflag | 0,"Parameter expecte after -s");
				msteps = atoi(na);
				if (msteps < 1 || msteps > 16)
					usage(uflag | 0,"-s parameter value %d is outside the range 1 to 16",msteps);

			/* Change color callout */
			} else if (argv[fa][1] == 'C') {
				fa = nfa;
				if (na == NULL) usage(uflag | 0,"Parameter expected after -C");
				ccallout = na;

			/* Measure color callout */
			} else if (argv[fa][1] == 'M') {
				fa = nfa;
				if (na == NULL) usage(uflag | 0,"Parameter expected after -M");
				mcallout = na;

			/* Serial port flow control */
			} else if (argv[fa][1] == 'W') {
				fa = nfa;
				if (na == NULL) usage(uflag | 0,"Parameter expected following -W");
				if (na[0] == 'n' || na[0] == 'N')
					fc = fc_None;
				else if (na[0] == 'h' || na[0] == 'H')
					fc = fc_Hardware;
				else if (na[0] == 'x' || na[0] == 'X')
					fc = fc_XonXOff;
				else
					usage(uflag | 0,"-W parameter '%c' not recognised",na[0]);

			} else if (argv[fa][1] == 'D') {
				debug = 1;
				if (na != NULL && na[0] >= '0' && na[0] <= '9') {
					debug = atoi(na);
					fa = nfa;
				}
				g_log->debug = debug;

			} else if (argv[fa][1] == 'I') {
				fa = nfa;
				if (na == NULL) usage(uflag | 0,"Expect argument to display description flag -I");
				displayname = strdup(na);

			} else if (argv[fa][1] == 't') {
				fa = nfa;
				if (na == NULL) usage(uflag | 2,"Expect argument to display technology flag -t");
				dtinfo = disptech_get_list();
				if (na[1] != '\000')
					usage(uflag | 2,"Expect single character argument to display technology flag -t");
				if ((dtinfo = disptech_select(dtinfo, na[0])) == NULL)
					usage(uflag | 2,"-t parameter '%c' not recognized",na[0]);

			/* Copyright string */
			} else if (argv[fa][1] == 'E') {
				fa = nfa;
				if (na == NULL) usage(uflag | 0,"Expect argument to overall description flag -E");
				description = strdup(na);

			/* Extra flags */
			} else if (argv[fa][1] == 'Y') {
				if (na == NULL)
					usage(uflag | 0,"Flag '-Y' expects extra flag");
			
				if (na[0] == 'r') {
					refrmode = 1;
				} else if (na[0] == 'n') {
					refrmode = 0;
				} else if (na[0] == 'R') {
					if (na[1] != ':')
						usage(uflag | 0,"-Y R:rate syntax incorrect");
					refrate = atof(na+2);
					if (refrate < 5.0 || refrate > 150.0)
						usage(uflag | 0,"-Y R:rate %f Hz not in valid range",refrate);
				} else if (na[0] == 'A') {
					nadaptive = 1;
				} else {
					usage(uflag | 0,"Flag '-Z %c' not recognised",na[0]);
				}
				fa = nfa;

			/* UI selection character */
			} else if (argv[fa][1] == 'U') {
				fa = nfa;
				if (na == NULL || na[0] == '\000') usage(uflag | 0,"Expect argument to flag -U");
				uisel = na;
				for (i = 0; uisel[i] != '\000'; i++) {
					if (!( (uisel[i] >= '0' && uisel[i] <= '9')
					    || (uisel[i] >= 'A' && uisel[i] <= 'Z')
					    || (uisel[i] >= 'a' && uisel[i] <= 'z'))) {
						usage(uflag | 0,"-U character(s) must be 0-9,A-Z,a-z");
					}
				}

			} else 
				usage(uflag | 0,"Flag '-%c' not recognised",argv[fa][1]);
		}
		else
			break;
	}

	/* Get the output ccmx file name argument */
	if (fa >= argc)
		usage(uflag | 0,"Output filname expected");


	strncpy(outname,argv[fa++],MAXNAMEL-1); outname[MAXNAMEL-1] = '\000';
	if (strrchr(outname, '.') == NULL)	/* no extension */
		strcat(outname, doccss ? ".ccss" : ".ccmx");

	if (fakeseq && doccss)
		error("Fake CCSS test not implemented");

	printf("\n");

	if (dtinfo == NULL)
		error("Display technology (-t) must be set");


	/* CCSS: See if we're working from a .ti3 file */
	if (doccss && innames[0][0] != '\000') {
		cgats *cgf = NULL;			/* cgats file data */
		int sidx;					/* Sample ID index */
		int ii, ti;
		char buf[100];
		int  spi[XSPECT_MAX_BANDS];	/* CGATS indexes for each wavelength */
		xspect sp, *samples = NULL;
		ccss *cc;
		double bigv = -1e60;

		if (innames[1][0] != '\000')
			warning("second -f .ti3 '%s' ignored! (not needed for CCSS)", innames[1]);

		/* Open spectral values file */
		cgf = new_cgats();			/* Create a CGATS structure */
		cgf->add_other(cgf, ""); 	/* Allow any signature file */
	
		if (cgf->read_name(cgf, innames[0]))
			error("CGATS file '%s' read error : %s",innames[0],cgf->e.m);
	
		if (cgf->ntables < 1)
			error ("Input file '%s' doesn't contain at least one table",innames[0]);
	
		if ((npat = cgf->t[0].nsets) <= 0)
			error("No sets of data in file '%s'",innames[0]);

		if ((samples = (xspect *)malloc(npat * sizeof(xspect))) == NULL)
				error("malloc failed");

		if ((ii = cgf->find_kword(cgf, 0, "TARGET_INSTRUMENT")) < 0)
			error ("Can't find keyword TARGET_INSTRUMENT in '%s'",innames[0]);

		if ((ti = cgf->find_kword(cgf, 0, "DISPLAY_TYPE_REFRESH")) >= 0) {
			if (stricmp(cgf->t[0].kdata[ti], "YES") == 0)
				refrmode = 1;
			else if (stricmp(cgf->t[0].kdata[ti], "NO") == 0)
				refrmode = 0;
		}

		if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_BANDS")) < 0)
			error ("Input file '%s' doesn't contain keyword SPECTRAL_BANDS",innames[0]);
		sp.spec_n = atoi(cgf->t[0].kdata[ii]);
		if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_START_NM")) < 0)
			error ("Input file '%s' doesn't contain keyword SPECTRAL_START_NM",innames[0]);
		sp.spec_wl_short = atof(cgf->t[0].kdata[ii]);
		if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_END_NM")) < 0)
			error ("Input file '%s' doesn't contain keyword SPECTRAL_END_NM",innames[0]);
		sp.spec_wl_long = atof(cgf->t[0].kdata[ii]);
		sp.norm = 1.0;		/* We assume emssive */

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

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

			if (cgf->t[0].ftype[spi[j]] != r_t)
				error("Field %s is wrong type - expect float",buf);
		}

		/* Transfer all the spectral values */
		for (i = 0; i < npat; i++) {

			XSPECT_COPY_INFO(&samples[i], &sp);
	
			for (j = 0; j < sp.spec_n; j++) {
				samples[i].spec[j] = *((double *)cgf->t[0].fdata[i][spi[j]]);
			}
		}
		cgf->del(cgf);		/* Clean up */
		cgf = NULL;

		if (description == NULL) {
			char *disp = displayname != NULL ? displayname : dtinfo->desc;
			char *tt = "CCSS for ";
			if ((description = malloc(strlen(disp) + strlen(tt) + 1)) == NULL)
				error("Malloc failed");
			strcpy(description, tt);
			strcat(description, disp);
		}

		/* See what the highest value is */
		for (i = 0; i < npat; i++) {	/* For all grid points */

			for (j = 0; j < samples[i].spec_n; j++) {
				if (samples[i].spec[j] > bigv)
					bigv = samples[i].spec[j];
			}
		}
			
		/* Normalize the values */
		for (i = 0; i < npat; i++) {	/* For all grid points */
			double scale = 100.0;

			for (j = 0; j < samples[i].spec_n; j++)
				samples[i].spec[j] *= scale / bigv;
		}
			
		if (refrmode < 0)
			error("The display refresh mode is not known - use the -Y flag");

		if ((cc = new_ccss()) == NULL)
			error("new_ccss() failed");

		if (cc->set_ccss(cc, "Argyll ccxxmake", NULL, description, displayname,
		                 dtinfo->dtech, refrmode, uisel, refname, 0, samples, npat)) {
			error("set_ccss failed with '%s'\n",cc->e.m);
		}
		if(cc->write_ccss(cc, outname))
			printf("\nWriting CCSS file '%s' failed with '%s\n",outname, cc->e.m);
		else
			printf("\nWriting CCSS file '%s' succeeded\n",outname);
		cc->del(cc);
		free(samples);

#ifdef DEBUG
		printf("About to exit\n");
#endif
		return 0;
	}

	/* CCMX: See if we're working from two files */
	if (!doccss && innames[0][0] != '\000') {
		int n;
		char *oname = NULL;			/* Observer name */
		ccmx *cc;

		if (innames[1][0] == '\000') {
			error("Need two -f .ti3 files to create CCMX");
		}

		/* Open up each CIE file in turn, target then measured, */
		/* and read in the CIE values. */
		for (n = 0; n < 2; n++) {
			cgats *cgf = NULL;			/* cgats file data */
			int isLab = 0;				/* 0 if file CIE is XYZ, 1 if is Lab */
			double wxyz[3], scale = 1.0;/* Scale factor back to absolute */
			int sidx;					/* Sample ID index */
			int xix, yix, zix;
			ary3 *current = NULL;		/* Current value array */
			int ii, ti;
			int instspec = 0;			/* File is spectrale */

			/* Open CIE target values */
			cgf = new_cgats();			/* Create a CGATS structure */
			cgf->add_other(cgf, ""); 	/* Allow any signature file */
		
			if (cgf->read_name(cgf, innames[n]))
				error("CGATS file '%s' read error : %s",innames[n],cgf->e.m);
		
			if (cgf->ntables < 1)
				error ("Input file '%s' doesn't contain at least one table",innames[n]);
		
			/* Check if the file is suitable */
			if (cgf->find_field(cgf, 0, "LAB_L") < 0
			 && cgf->find_field(cgf, 0, "XYZ_X") < 0) {
		
				error ("No CIE data found in file '%s'",innames[n]);
			}
		
			if (cgf->find_field(cgf, 0, "LAB_L") >= 0)
				isLab = 1;
			
			if (cols == NULL) {
				if ((npat = cgf->t[0].nsets) <= 0)
					error("No sets of data in file '%s'",innames[n]);

				if ((refs = (ary3 *)malloc(npat * sizeof(ary3))) == NULL)
					error("malloc failed");
				if ((cols = (ary3 *)malloc(npat * sizeof(ary3))) == NULL)
					error("malloc failed");

			} else {
				if (npat != cgf->t[0].nsets)
					error ("Number of sets %d in file '%s' doesn't match other file %d",cgf->t[0].nsets,innames[n],npat);
			}

			if ((ii = cgf->find_kword(cgf, 0, "TARGET_INSTRUMENT")) < 0)
				error ("Can't find keyword TARGET_INSTRUMENT in '%s'",innames[n]);
	
			if ((ti = cgf->find_kword(cgf, 0, "INSTRUMENT_TYPE_SPECTRAL")) < 0)
				error ("Can't find keyword INSTRUMENT_TYPE_SPECTRAL in '%s'",innames[n]);
	
			if (strcmp(cgf->t[0].kdata[ti],"YES") == 0) {
				instspec = 1;		/* Currently is a spectral file */
				if (gotref)
					error("Found two spectral files - expect one colorimtric file");
				current = refs;
				refname = strdup(cgf->t[0].kdata[ii]);
				reffile = innames[n];
				gotref = 1;
				
			} else if (strcmp(cgf->t[0].kdata[ti],"NO") == 0) {
				instspec = 0;			/* Currently is not spectral file */
				if (gotcol) {
					/* Copy what we though was cols to refs */
					refname = colname;
					reffile = colfile;
					for (i = 0; i < npat; i++) {
						refs[i][0] = cols[i][0];
						refs[i][1] = cols[i][1];
						refs[i][2] = cols[i][2];
					}
					gotref = 1;
					warning("Got two colorimetric files - assuming '%s' is the reference",innames[0]);
					refrmode = -1;
					cbid = 0;

					if (spec) {
						error("Spectral reference is required to use non-standard observer");
					}
				}
				if ((ti = cgf->find_kword(cgf, 0, "DISPLAY_TYPE_REFRESH")) >= 0) {
					if (stricmp(cgf->t[0].kdata[ti], "YES") == 0)
						refrmode = 1;
					else if (stricmp(cgf->t[0].kdata[ti], "NO") == 0)
						refrmode = 0;
				}
				if ((ti = cgf->find_kword(cgf, 0, "DISPLAY_TYPE_BASE_ID")) >= 0) {
					cbid = atoi(cgf->t[0].kdata[ti]);
				} else {
					cbid = 0;
				}
				current = cols;
				colname = strdup(cgf->t[0].kdata[ii]);
				colfile = innames[n];
				gotcol = 1;
			} else {
				error ("Unknown INSTRUMENT_TYPE_SPECTRAL value '%s'",cgf->t[0].kdata[ti]);
			}

			if ((ti = cgf->find_kword(cgf, 0, "NORMALIZED_TO_Y_100")) < 0
			 || strcmp(cgf->t[0].kdata[ti],"NO") == 0) {
				scale = 1.0;		/* Leave absolute */
			} else {
				if ((ti = cgf->find_kword(cgf, 0, "LUMINANCE_XYZ_CDM2")) < 0)
					error ("Can't find keyword LUMINANCE_XYZ_CDM2 in '%s'",innames[n]);
				if (sscanf(cgf->t[0].kdata[ti],"%lf %lf %lf", &wxyz[0], &wxyz[1], &wxyz[2]) != 3)
					error ("Unable to parse LUMINANCE_XYZ_CDM2 in '%s'",innames[n]);
				scale = wxyz[1]/100.0;	/* Convert from Y = 100 normalise back to absolute */
			}

			if (instspec && spec) {
				int ii;
				xspect sp;
				char buf[100];
				int  spi[XSPECT_MAX_BANDS];	/* CGATS indexes for each wavelength */
				xsp2cie *sp2cie;			/* Spectral conversion object */

				if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_BANDS")) < 0)
					error ("Input file '%s' doesn't contain keyword SPECTRAL_BANDS",innames[n]);
				sp.spec_n = atoi(cgf->t[0].kdata[ii]);
				if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_START_NM")) < 0)
					error ("Input file '%s' doesn't contain keyword SPECTRAL_START_NM",innames[n]);
				sp.spec_wl_short = atof(cgf->t[0].kdata[ii]);
				if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_END_NM")) < 0)
					error ("Input file '%s' doesn't contain keyword SPECTRAL_END_NM",innames[n]);
				sp.spec_wl_long = atof(cgf->t[0].kdata[ii]);
				sp.norm = 1.0;		/* We assume emssive */

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

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

				/* Create a spectral conversion object */
				if ((sp2cie = new_xsp2cie(icxIT_none, 0.0, NULL, obType, custObserver, icSigXYZData, icxClamp)) == NULL)
					error("Creation of spectral conversion object failed");

				for (i = 0; i < npat; i++) {
	
					/* Read the spectral values for this patch */
					for (j = 0; j < sp.spec_n; j++) {
						sp.spec[j] = *((double *)cgf->t[0].fdata[i][spi[j]]) * scale;
					}
	
					/* Convert it to CIE space */
					sp2cie->convert(sp2cie, current[i], &sp);
				}
				sp2cie->del(sp2cie);        /* Done with this */

			/* Colorimetric file - assume it's the target */
			} else {

				if (isLab) {		/* Expect Lab */
					if ((xix = cgf->find_field(cgf, 0, "LAB_L")) < 0)
						error("Input file '%s' doesn't contain field LAB_L",innames[n]);
					if (cgf->t[0].ftype[xix] != r_t)
						error("Field LAB_L is wrong type - expect float");
					if ((yix = cgf->find_field(cgf, 0, "LAB_A")) < 0)
						error("Input file '%s' doesn't contain field LAB_A",innames[n]);
					if (cgf->t[0].ftype[yix] != r_t)
						error("Field LAB_A is wrong type - expect float");
					if ((zix = cgf->find_field(cgf, 0, "LAB_B")) < 0)
						error("Input file '%s' doesn't contain field LAB_B",innames[n]);
					if (cgf->t[0].ftype[zix] != r_t)
						error("Field LAB_B is wrong type - expect float");

				} else { 		/* Expect XYZ */
					if ((xix = cgf->find_field(cgf, 0, "XYZ_X")) < 0)
						error("Input file '%s' doesn't contain field XYZ_X",innames[n]);
					if (cgf->t[0].ftype[xix] != r_t)
						error("Field XYZ_X is wrong type - expect float");
					if ((yix = cgf->find_field(cgf, 0, "XYZ_Y")) < 0)
						error("Input file '%s' doesn't contain field XYZ_Y",innames[n]);
					if (cgf->t[0].ftype[yix] != r_t)
						error("Field XYZ_Y is wrong type - expect float");
					if ((zix = cgf->find_field(cgf, 0, "XYZ_Z")) < 0)
						error("Input file '%s' doesn't contain field XYZ_Z",innames[n]);
					if (cgf->t[0].ftype[zix] != r_t)
						error("Field XYZ_Z is wrong type - expect float");
				}

				for (i = 0; i < npat; i++) {
					current[i][0] = *((double *)cgf->t[0].fdata[i][xix]);
					current[i][1] = *((double *)cgf->t[0].fdata[i][yix]);
					current[i][2] = *((double *)cgf->t[0].fdata[i][zix]);
					if (isLab) {	/* Convert test patch Lab to XYZ scale 100 */
						icmLab2XYZ(&icmD50_100, current[i], current[i]);
					}
					/* Rescale to absolute if needed */
					current[i][0] *= scale;
					current[i][1] *= scale;
					current[i][2] *= scale;
				}
			}
			cgf->del(cgf);		/* Clean up */
			cgf = NULL;
		}

		if (spec != 0 && obType != icxOT_CIE_1931_2)
			oname = standardObserverDescription(obType);

		if (oname != NULL) {
			char *tt = colname;

			if ((colname = malloc(strlen(tt) + strlen(oname) + 3)) == NULL)
				error("Malloc failed");
			strcpy(colname, tt);
			strcat(colname, " (");
			strcat(colname, oname);
			strcat(colname, ")");
		}
		if (description == NULL) {
			char *disp = displayname != NULL ? displayname : dtinfo->desc;
			if ((description = malloc(strlen(colname) + strlen(disp) + 4)) == NULL)
				error("Malloc failed");
			strcpy(description, colname);
			strcat(description, " & ");
			strcat(description, disp);
		}

		if (refrmode < 0)
			error("The display refresh mode is not in '%s' - use the -Y flag",colfile);

		if (cbid == 0)
			error("The calibration base display mode not specified in the '%s' file",colfile);

		if ((cc = new_ccmx()) == NULL)
			error("new_ccmx() failed");

		if (cc->create_ccmx(cc, description, colname, displayname, dtinfo->dtech,
			                     refrmode, cbid, uisel, refname, 0, npat, refs, cols)) {
			error("create_ccmx failed with '%s'\n",cc->e.m);
		}
		if (verb) {
			printf("Fit error is max %f, avg %f DE94\n",cc->mx_err,cc->av_err);
			printf("Correction matrix is:\n");
			printf("  %f %f %f\n", cc->matrix[0][0], cc->matrix[0][1], cc->matrix[0][2]);
			printf("  %f %f %f\n", cc->matrix[1][0], cc->matrix[1][1], cc->matrix[1][2]);
			printf("  %f %f %f\n", cc->matrix[2][0], cc->matrix[2][1], cc->matrix[2][2]);
		}

		if(cc->write_ccmx(cc, outname))
			printf("\nWriting CCMX file '%s' failed with '%s'\n",outname,cc->e.m);
		else
			printf("\nWriting CCMX file '%s' succeeded\n",outname);
		cc->del(cc);

	/* Do interactive measurements */
	} else {

		/* No explicit display has been set */
		if (
#ifndef SHOW_WINDOW_ONFAKE
		!fake 
#endif
#ifdef NT
		 && madvrdisp == 0
#endif
		 && dummydisp == 0
		 && webdisp == 0
		 && ccdisp == 0
		 && disp == NULL) {
			int ix = 0;
#if defined(UNIX_X11)
			char *dn, *pp;

			if ((dn = getenv("DISPLAY")) != NULL) {
				if ((pp = strrchr(dn, ':')) != NULL) {
					if ((pp = strchr(pp, '.')) != NULL) {
						if (pp[1] != '\000')
							ix = atoi(pp+1);
					}
				}
			}
#endif
			if ((disp = get_a_display(ix)) == NULL)
				error("Unable to open the default display");

			if (displayname == NULL && (displayname = strdup(disp->description)) == NULL)
				error("Malloc failed");

			printf("Display description is '%s'\n",displayname);
		}
		if (fake) {
			displayname = strdup("fake display");
		}

		/* If we've requested ChromeCast, look it up */
		if (ccdisp) {
			if ((ccids = get_ccids()) == NULL)
				error("discovering ChromCasts failed");
			if (ccids[0] == NULL)
				error("There are no ChromCasts to use\n");
			for (i = 0; ccids[i] != NULL; i++)
				;
			if (ccdisp < 1 || ccdisp > i)
				error("Chosen ChromCasts (%d) is outside list (1..%d)\n",ccdisp,i);
			ccid = ccids[ccdisp-1];
		}

		/* Create grid of device test values */
		{
			int j;
			int gc[3];			/* Grid coordinate */

			if (msteps == 1)
				npat = 4;
			else
				npat = msteps * msteps * msteps;

			if ((rdcols = (col *)malloc(npat * sizeof(col))) == NULL) {
				error("malloc failed");
			}
			if ((refs = (ary3 *)malloc(npat * sizeof(ary3))) == NULL) {
				free(rdcols);
				error("malloc failed");
			}
			if ((cols = (ary3 *)malloc(npat * sizeof(ary3))) == NULL) {
				free(rdcols);
				free(refs);
				error("malloc failed");
			}

			/* RGBW */
			if (msteps == 1) {
				npat = 0;
				rdcols[npat].r = 1.0;
				rdcols[npat].g = 0.0;
				rdcols[npat].b = 0.0;
				npat++;
				rdcols[npat].r = 0.0;
				rdcols[npat].g = 1.0;
				rdcols[npat].b = 0.0;
				npat++;
				rdcols[npat].r = 0.0;
				rdcols[npat].g = 0.0;
				rdcols[npat].b = 1.0;
				npat++;
				rdcols[npat].r = 1.0;
				rdcols[npat].g = 1.0;
				rdcols[npat].b = 1.0;
				npat++;
#ifdef DEBUG
				for (j = 0; j < 4; j++) 
					printf("Dev val %f %f %f\n",rdcols[j].r,rdcols[j].g,rdcols[j].b);
#endif
			} else {
				for (j = 0; j < 3; j++)
					gc[j] = 0;			/* init coords */
					
				for (npat = 0; ;) {	/* For all grid points */

					/* Just colors with at least one channel at 100% */
					if (gc[0] == (msteps-1)
					 || gc[1] == (msteps-1)
					 || gc[2] == (msteps-1)) 
					{
						
						rdcols[npat].r = (double)gc[0]/(msteps-1);
						rdcols[npat].g = (double)gc[1]/(msteps-1);
						rdcols[npat].b = (double)gc[2]/(msteps-1);
#ifdef DEBUG
						printf("Dev val %f %f %f\n",rdcols[npat].r,rdcols[npat].g,rdcols[npat].b);
#endif
						npat++;
					}
					
					/* Increment grid index and position */
					for (j = 0; j < 3; j++) {
						gc[j]++;
						if (gc[j] < msteps)
							break;	/* No carry */
						gc[j] = 0;
					}
					if (j >= 3)
						break;		/* Done grid */
				}
			}
			if (verb)
				printf("Total test patches = %d\n",npat);
		}

		/* Until the measurements are done, or we give up */
		for (;;) {
			int c;

			/* Print the menue of adjustments */
			printf("\n");
			if (gotref)
				printf("[Got spectrometer readings]\n");
			if (gotcol)
				printf("[Got colorimeter readings]\n");
			printf("Press 1 .. 4:\n");
			{
				printf("1) Select an instrument, Currently %d (", comno);
				if (icmps == NULL)
					icmps = new_icompaths(g_log);
				else
					icmps->refresh(icmps);
				if (icmps != NULL) {
					icompath **paths;
					if ((paths = icmps->paths) != NULL) {
						int i;
						for (i = 0; ; i++) {
							if (paths[i] == NULL)
								break;
							if ((i+1) == comno) {
								printf(" '%s'",paths[i]->name);
								break;
							}
						}
					}
				}
				printf(")\n");
			}
			if (doccss)
				printf("2) Measure test patches with current (spectrometer) instrument\n");
			else
				printf("2) Measure test patches with current instrument\n");

			if (doccss) {
				if (gotref)
					printf("3) Save Colorimeter Calibration Spectral Set\n");
				else
					printf("3) [ Save Colorimeter Calibration Spectral Set ]\n");

			} else {
				if (gotref && gotcol)
					printf("3) Compute Colorimeter Correction Matrix & save it\n");
				else
					printf("3) [ Compute Colorimeter Correction Matrix & save it ]\n");
			}
			printf("4) Exit\n");

			if (fakeseq == 0) {
				empty_con_chars();
				c = next_con_char();
			} else {
				switch (fakeseq) {
					case 1:
						c = '2';
						fakeseq = 2;
						break;
					case 2:
						c = '2';
						fakeseq = 3;
						break;
					case 3:
						c = '3';
						fakeseq = 4;
						break;
					default:
						c = '4';
						break;
				}
			}
			printf("'%c'\n",c);


			/* Deal with selecting the instrument */
			if (c == '1') {
				if (icmps == NULL)
					icmps = new_icompaths(g_log);
				else
					icmps->refresh(icmps);
				if (icmps != NULL) {
					icompath **paths;
					if ((paths = icmps->paths) != NULL) {
						int i;
						for (i = 0; ; i++) {
							if (paths[i] == NULL)
								break;
							if ((paths[i]->dtype == instSpyder1 && setup_spyd2(0) == 0)
							 || (paths[i]->dtype == instSpyder2 && setup_spyd2(1) == 0))
								fprintf(stderr,"    %d = '%s' !! Disabled - no firmware !!\n",i+1,paths[i]->name);
							else
								fprintf(stderr,"    %d = '%s'\n",i+1,paths[i]->name);
						}
						printf("Select device 1 - %d: \n",i);
						empty_con_chars();
						c = next_con_char();

						if (c < '1' || c > ('0' + i)) {
							printf("'%c' is out of range - ignored !\n",c);
						} else {
							comno = c - '0'; 
						}
						
					} else {
						fprintf(stderr,"No ports to select from!\n");
					}
				}
				continue;
			}

			/* Deal with doing a measurement */
			if (c == '2') {
				int errc;				/* Return value from new_disprd() */
				disprd *dr;				/* Display patch read object */
				inst *it;				/* Instrument */
				inst_mode  cap = inst_mode_none;	/* Instrument mode capabilities */
				inst2_capability cap2 = inst2_none;	/* Instrument capabilities 2 */
				inst3_capability cap3 = inst3_none;	/* Instrument capabilities 3 */

				if (fake)
					comno = FAKE_DEVICE_PORT;
				if (icmps == NULL)
					icmps = new_icompaths(g_log);

				/* Should we use current cal rather than native ??? */
				if ((dr = new_disprd(&errc, icmps->get_path(icmps, comno),
				                     fc, ditype, sditype, 1, tele, ambient, nadaptive,
				                     noinitcal, 0, highres, refrate, 3, NULL, NULL,
					                 NULL, 0, disp, icalmax, 0, fullscreen,
				                     override, webdisp, ccid,
#ifdef NT
					                 madvrdisp,
#endif
									 dummydisp, ccallout, mcallout, 0,
					                 100.0 * hpatscale, 100.0 * vpatscale, ho, vo,
					                 disptech_unknown, 0, NULL, NULL, 0, 2, icxOT_default, NULL, 
				                     0, 0, "fake" ICC_FILE_EXT, g_log)) == NULL) {
					printf("Opening the instrument failed with '%s'. Try selecting it again ?\n",
					        disprd_err(errc));
					continue;
				}
			
				it = dr->it;

				if (fake) {
					if (faketoggle)
						cap = inst_mode_spectral;
					else
						cap = inst_mode_colorimeter;
					cap2 = inst2_none;
					cap3 = inst3_none;
					refrmode = 0; 
					cbid = 1;
				} else {
					it->capabilities(it, &cap, &cap2, &cap3);
					if (!IMODETST(cap, inst_mode_spectral)) {
						dr->get_disptype(dr, &refrmode, &cbid);	/* Get the display type info */
					}
				}

				if (doccss && !IMODETST(cap, inst_mode_spectral)) {
					printf("You have to use a spectrometer to create a CCSS!\n");
					continue;
				}

				if (faketoggle)
					dr->fake2 = 1;
				else
					dr->fake2 = -1;

				/* Test the CRT with all of the test points */
				if ((rv = dr->read(dr, rdcols, npat, 1, npat, 1, 0, instClamp, 0)) != 0) {
					dr->del(dr);
					error("disprd returned error code %d\n",rv);
				}

				if (doccss) {		/* We'll use the rdcols values */
					gotref = 1;
				} else {
					if (IMODETST(cap, inst_mode_spectral)) {
						xsp2cie *sp2cie = NULL;

						if (spec) {
							/* Create a spectral conversion object */
							if ((sp2cie = new_xsp2cie(icxIT_none, 0.0, NULL, obType, custObserver, icSigXYZData, icxClamp)) == NULL)
								error("Creation of spectral conversion object failed");
						}
						for (i = 0; i < npat; i++) {	/* For all grid points */
							if (spec) {
								if (rdcols[i].sp.spec_n <= 0)
									error("Didn't get spectral value");
								sp2cie->convert(sp2cie, refs[i], &rdcols[i].sp);
							} else {
								if (rdcols[i].XYZ_v == 0)
									error("Didn't get XYZ value");
								refs[i][0] = rdcols[i].XYZ[0];
								refs[i][1] = rdcols[i].XYZ[1];
								refs[i][2] = rdcols[i].XYZ[2];
							}
						}
						if (fake)
							refname = "fake spectrometer";
						else
							refname = inst_name(it->dtype);
						gotref = 1;
						if (sp2cie != NULL)
							sp2cie->del(sp2cie);
					} else if (IMODETST(cap, inst_mode_colorimeter)) {
						for (i = 0; i < npat; i++) {	/* For all grid points */
							if (rdcols[i].XYZ_v == 0)
								error("Didn't get XYZ value");
							cols[i][0] = rdcols[i].XYZ[0];
							cols[i][1] = rdcols[i].XYZ[1];
							cols[i][2] = rdcols[i].XYZ[2];
						}
						if (fake)
							colname = "fake colorimeter";
						else
							colname = inst_name(it->dtype);
						gotcol = 1;
					}
				}
				dr->del(dr);

				faketoggle ^= 1;

			}	/* End of take a measurement */

			if (c == '3') {		/* Compute result and save */
				/* Save the CCSS */
				if (doccss) {
					ccss *cc;
					xspect *samples = NULL;
					double bigv = -1e60;

					if (!gotref) {
						printf("You have to read the spectrometer values first!\n");
						continue;
					}

					if (description == NULL) {
						char *disp = displayname != NULL ? displayname : dtinfo->desc;
						char *tt = "CCSS for ";
						if ((description = malloc(strlen(disp) + strlen(tt) + 1)) == NULL)
							error("Malloc failed");
						strcpy(description, tt);
						strcat(description, disp);
					}

					if ((samples = (xspect *)malloc(sizeof(xspect) * npat)) == NULL)
						error("Malloc failed");
					
					/* See what the highest value is */
					for (i = 0; i < npat; i++) {	/* For all grid points */
						if (rdcols[i].sp.spec_n <= 0)
							error("Didn't get spectral values");
						for (j = 0; j < rdcols[i].sp.spec_n; j++) {
							if (rdcols[i].sp.spec[j] > bigv)
								bigv = rdcols[i].sp.spec[j];
						}
					}
						
					/* Copy all the values and normalize them */
					for (i = 0; i < npat; i++) {	/* For all grid points */
						double scale = 100.0;

						samples[i] = rdcols[i].sp;	/* Structure copy */
						for (j = 0; j < rdcols[i].sp.spec_n; j++)
							samples[i].spec[j] *= scale / bigv;
					}
						
					if (refrmode < 0)
						warning("No refresh mode specified! Assuming non-refresh !");

					if ((cc = new_ccss()) == NULL)
						error("new_ccss() failed");
	
					if (cc->set_ccss(cc, "Argyll ccxxmake", NULL, description, displayname,
					                 dtinfo->dtech, refrmode, NULL, refname, 0, samples, npat)) {
						error("set_ccss failed with '%s'\n",cc->e.m);
					}
					if(cc->write_ccss(cc, outname))
						printf("\nWriting CCSS file '%s' failed with '%s'\n",outname,cc->e.m);
					else
						printf("\nWriting CCSS file '%s' succeeded\n",outname);
					cc->del(cc);
					free(samples);
					saved = 1;

				/* Compute and save CCMX */
				} else {
					char *oname = NULL;		/* Observer desciption */
					ccmx *cc;
	
					if (!gotref) {
						printf("You have to read the spectrometer values first!\n");
						continue;
					}
					if (!gotcol) {
						printf("You have to read the colorimeter values first!\n");
						continue;
					}
	
					if (spec != 0 && obType != icxOT_CIE_1931_2)
						oname = standardObserverDescription(obType);
	
					if (oname != NULL) {		/* Incorporate observer name in colname */
						char *tt = colname;
	
						if ((colname = malloc(strlen(tt) + strlen(oname) + 3)) == NULL)
							error("Malloc failed");
						strcpy(colname, tt);
						strcat(colname, " (");
						strcat(colname, oname);
						strcat(colname, ")");
					}
					if (description == NULL) {
						char *disp = displayname != NULL ? displayname : dtinfo->desc;
						if ((description = malloc(strlen(colname) + strlen(disp) + 4)) == NULL)
							error("Malloc failed");
						strcpy(description, colname);
						strcat(description, " & ");
						strcat(description, disp);
					}

					if (refrmode < 0)
						error("Internal error - the instrument did not return a refmode");
					if (cbid == 0)
						error("Internal error - the instrument did not return a cbid");

					if ((cc = new_ccmx()) == NULL)
						error("new_ccmx() failed");
	
					if (cc->create_ccmx(cc, description, colname, displayname, dtinfo->dtech,
					                    refrmode, cbid, uisel, refname, 0, npat, refs, cols)) {
						error("create_ccmx failed with '%s'\n",cc->e.m);
					}
					if (verb) {
						printf("Fit error is avg %f, max %f DE94\n",cc->av_err,cc->mx_err);
						printf("Correction matrix is:\n");
						printf("  %f %f %f\n", cc->matrix[0][0], cc->matrix[0][1], cc->matrix[0][2]);
						printf("  %f %f %f\n", cc->matrix[1][0], cc->matrix[1][1], cc->matrix[1][2]);
						printf("  %f %f %f\n", cc->matrix[2][0], cc->matrix[2][1], cc->matrix[2][2]);
					}
	
					if(cc->write_ccmx(cc, outname))
						printf("\nWriting CCMX file '%s' failed with '%s'\n",outname,cc->e.m);
					else
						printf("\nWriting CCMX file '%s' succeeded\n",outname);
					cc->del(cc);
					saved = 1;
				}
			}

			if (c == '4' || c == 0x3) {		/* Exit */
				if (!saved) {
					printf("Not saved yet, are you sure ? (y/n): %s",fl_end); do_fflush();
					empty_con_chars();
					c = next_con_char();
					printf("\n");
					if (c != 'y' && c != 'Y')
						continue;
				}
				break;
			}

		}	/* Next command */

		free(displayname);
		if (icmps != NULL)
			icmps->del(icmps);
		free_ccids(ccids);
	}

#ifdef DEBUG
	/* Do a CCMX verification */
	if (!doccss) {
		ccmx *cc;
		double av_err, mx_err;
		int wix;
		double maxy = -1e6;
		icmXYZNumber wh;

		for (i = 0; i < npat; i++) {
			if (refs[i][1] > maxy) {
				maxy = refs[i][1];
				wix = i;
			}
		}
		wh.X = refs[wix][0];
		wh.Y = refs[wix][1];
		wh.Z = refs[wix][2];

		if ((cc = new_ccmx()) == NULL)
			error("new_ccmx() failed");
		if(cc->read_ccmx(cc, outname))
			printf("Reading file '%s' failed\n",outname);

		av_err = mx_err = 0.0;
		for (i = 0; i < npat; i++) {
			double txyz[3], tlab[3], xyz[3], _xyz[3], lab[3], de;
			icmCpy3(txyz, refs[i]);
			icmXYZ2Lab(&wh, tlab, txyz);
			icmCpy3(xyz, cols[i]);
			cc->xform(cc,_xyz, xyz);
			icmXYZ2Lab(&wh, lab, _xyz);
			de = icmCIE94(tlab, lab);
			av_err += de;
			if (de > mx_err)
				mx_err = de;
			printf("%d: txyz %f %f %f\n",i,txyz[0], txyz[1], txyz[2]);
			printf("%d:  xyz %f %f %f, _xyz %f %f %f\n",i,xyz[0], xyz[1], xyz[2], _xyz[0], _xyz[1], _xyz[2]);
			printf("%d: tlab %f %f %f, lab %f %f %f\n",i,tlab[0], tlab[1], tlab[2], lab[0], lab[1], lab[2]);
			printf("%d: de %f\n",i,de);
		}
		av_err /= npat;
		printf("Avg = %f, max = %f\n",av_err,mx_err);
		cc->del(cc);
	}
#endif

#ifdef DEBUG
	printf("About to exit\n");
#endif

	return 0;
}






