#define VALUE_B (bval)
#define VALUE_S (val)
#define VALUE_I atoi(val)
#define VALUE_F atof(val)
#define BOOLEAN(x) ((x)=(bval==1?1:0))
#define RETURN return(1)
#define FAIL return(-1)

global char *CurrentFile = NULL;
global int StackCount = 0;
global int HaveWindow = 0;

help
	int i;
	Print("\nUsage: ninox [ switches] [files or directories]\n\n");
	Print("Switches: \n\n");
	for(i=0; Names[i].func != NULL; ++i)
	   if (! strstr(Names[i].option,"help-"))
		Print("-%s ",Names[i].option);
	Print("\n\n");
	Print("Use \"ninox -help-X\" to see detailed help about switch X\n\n");
	exit(0);
	
config inifile ini
# Allows a set of switches to be given in a file. Format is:
#       [ninox]
#       switch=value
#       switch=value
#       ...
#
# Switches are given without the leading '-'
#
# The ini file is included with "-ini=/path/to/file"

global int LoadedConfig = 0;
	char *params[128],*vals[128];
	char *c,*str,*cfgfile;
	int i,n,rval;

	cfgfile = strtok(val,",");
	if (! cfgfile || cfgfile[0]==0) {
	   printf("Error: Invalid filename for -config\n");
	   exit(1);
	   }

	if (access(cfgfile,R_OK)) {
	   printf("Error: Cannot find config file '%s'\n",cfgfile);
	   exit(1);
	   }

	// parse remainder of val into var=value tokens
	n=0;
#if 0
	while(str = strtok(NULL,",")) {
	   char *c = strchr(str,'=');
	   if (c) {
		*c=0;
		params[n] = Strdup(str);
		vals[n++] = Strdup(c+1);
		}
	   }

	params[n]=NULL;
	vals[n]=NULL;

	if (n) rval=LoadConfigFile(cfgfile,"ninox",params,vals);
	else
#endif
	rval=LoadConfigFile(cfgfile,"ninox",NULL,NULL);

	return rval;

antighost ag
global int AntiGhostEnable = 0;
global int AG_X = 0;
global int AG_Y = 0;
global int AG_MAG = 0;   // value from 1 .. 255, calculated as 1/255 .. 255/255
	int ch;

	if (sscanf(VALUE_S,"%d,%d,%d",&AG_X,&AG_Y,&AG_MAG) == 3) {
	   AntiGhostEnable = 1;

	   Print("Enabling Antighost: x=%d, y=%d val=%d\n",AG_X,AG_Y,AG_MAG);
	   }

overridechannel
# can be used to explicitly set a range of frames to a specific channel, overriding
# the automatic filename-based detection
global int OverrideChannel=0;
global int OverrideChannel_Value = 0;
global int OverrideChannel_Min = 0;
global int OverrideChannel_Max = 0;
	int ch;
	int min,max;

	if (sscanf(VALUE_S,"%d=%d:%d",&ch,&min,&max) == 3) {
		OverrideChannel = 1;
		OverrideChannel_Value = ch;
		OverrideChannel_Min = min;
		OverrideChannel_Max = max;

		}

frame-time frametime
# Used to supply the exposure time of each frame when the
# timestamp data is missing
global double FrameTime[10] = {0,0,0,0,0,0,0,0,0,0};
	int ch;
	double f;
	if (sscanf(VALUE_S,"%d,%lf",&ch,&f) == 2) FrameTime[ch] = f;
	if (sscanf(VALUE_S,"%d,auto",&ch)==1) {
	   // Look for and load the Shutter field from the preferences file
	   FrameTime[ch] = -1;
	   }

inputfilter-display ifd
# Display the action of the input filter
global int InputFilter_Display = 0;
	BOOLEAN(InputFilter_Display);

fft-ref
global char *FFT_Ref = NULL;
	FFT_Ref = Strdup(VALUE_S);
	DoFFT = 1;

dofft
global int DoFFT = 0;
global double FFT_LPF_Radius1 = 0;
global double FFT_LPF_Radius2 = 0;
global double FFT_LPF_Pow = 0;
	double r1,r2,p;

	if (sscanf(VALUE_S,"%lf,%lf,%lf",&r1,&r2,&p) == 3) {
	   /* 3 operand version, all params given */
	   FFT_LPF_Radius1 = r1;
	   FFT_LPF_Radius2 = r2;
	   FFT_LPF_Pow = p;
	   }
	else if (sscanf(VALUE_S,"%lf,%lf",&r1,&r2) == 2) {
	   /* 2 operand version, assume p = 1 */
	   FFT_LPF_Radius1 = r1;
	   FFT_LPF_Radius2 = r2;
	   FFT_LPF_Pow = 1.0;
	   }
	else if (sscanf(VALUE_S,"%lf",&r1) == 1) {
	   /* single operand version, assume r2 = r1 * 2 and p = 1 */
	   FFT_LPF_Radius1 = r1;
	   FFT_LPF_Radius2 = r1 * 2.0;
	   FFT_LPF_Pow = 1.0;
	   }
	else {
	   FFT_LPF_Radius1 = 0;
	   FFT_LPF_Radius2 = 0;
	   FFT_LPF_Pow = 0;
	   }
	   
	DoFFT = 1;
	
fft-filter
global int FFT_Filter=0;
	double r1,r2,p;

	if (sscanf(VALUE_S,"%lf,%lf,%lf",&r1,&r2,&p) == 3) {
	   /* 3 operand version, all params given */
	   FFT_LPF_Radius1 = r1;
	   FFT_LPF_Radius2 = r2;
	   FFT_LPF_Pow = p;
	   }
	else if (sscanf(VALUE_S,"%lf,%lf",&r1,&r2) == 2) {
	   /* 2 operand version, assume p = 1 */
	   FFT_LPF_Radius1 = r1;
	   FFT_LPF_Radius2 = r2;
	   FFT_LPF_Pow = 1.0;
	   }
	else if (sscanf(VALUE_S,"%lf",&r1) == 1) {
	   /* single operand version, assume r2 = r1 * 2 and p = 1 */
	   FFT_LPF_Radius1 = r1;
	   FFT_LPF_Radius2 = r1 * 2.0;
	   FFT_LPF_Pow = 1.0;
	   }
	else {
	   Print("fft-filter: Error, usage is fft-filter=r1,r2,p\n");
	   exit(1);
	   }

	FFT_Filter = 1;
	   
flux
global int SumFlux = 0;
global char * FluxFile = NULL;
	SumFlux = 1;
	FluxFile = VALUE_S;

fft_scan fft-scan
global int FFT_Region = 10;
	FFT_Region = VALUE_I;

mean3 mean3x3
# Enable a 3x3 mean smoothing on the image
global int Do3x3Smooth=0;
	BOOLEAN(Do3x3Smooth);

chain
# When playing or processing FITS Archive files (.fta), automatically
# chain from the end of one archive to the start of the next
global int ChainArchives=1;
	BOOLEAN(ChainArchives);

amean
# Enable the alpha-trimmed-mean smoothing algorithm
global int AMean=0;
	BOOLEAN(AMean);

i
# Enter interactive mode
global int InteractiveMode=0;
	BOOLEAN(InteractiveMode);

archiveoverridefilenames
# no help available
global int ArchiveOverrideFilenames=0;
	BOOLEAN(ArchiveOverrideFilenames);

enablecutout cutout
# ninox will attempt to locate and centre the dominant object in the
# frame by finding the centre of brightness and "cutting out" a rectangle
# around that point and moving it to the centre of the image.
global int DoCutout=1;
	BOOLEAN(DoCutout);
	if (! VALUE_B) ForceProcess=1;

maxmovement mm
# A frame will be marked as broken if there is more than this much
# movement from the last frame to this one
global int MaxMovement = 10;
	MaxMovement = VALUE_I;

detectbrokenframes dbf
# ninox will use an algorithm to detect corrupted (broken) frames
# by comparing each frame to its predecessor. Sudden large-scale
# changes are interpreted as corruption and the frame is skipped.
global int DetectBrokenFrames = 0;
global int DBF_TYPE = DBF_PLANET;

	if (!strcasecmp(VALUE_S,"planet")) DBF_TYPE = DBF_PLANET;
	else if (!strcasecmp(VALUE_S,"lunar")) DBF_TYPE = DBF_LUNAR;
	else if (!strcasecmp(VALUE_S,"moon")) DBF_TYPE = DBF_LUNAR;

	DetectBrokenFrames = 1;

dbftrigger
# With detect broken frames, this sets a threshhold for how many 
# broken triggers must be found in a single frame to mark it 
# as broken
global int DBF_TRIGGER = 10;
	DBF_TRIGGER = VALUE_I;

dbfedgecount
# With detectbrokenframes, this sets a threshhold for detecting
# broken frames.
global int DBF_EDGE_COUNT = 50;
	DBF_EDGE_COUNT = VALUE_I;

detecthotpixels dhp
global int DetectHotPixels = 0;
	BOOLEAN(DetectHotPixels);

process
# Useful on multi-core or multi-processor machines. Allows you to specify
# a subset of the available images to be processed on the assumption that
# another invocation of ninox will be processing the skipped images.
#
# Usage: -process=offset,skip
#
# Offset is the starting image number (0 = first)
# Skip is the number of images to skip after processing an image (default=0)
#
# eg, to run 2 copies of ninox, use:
#
#	ninox -process=0,1 ...
# and
#	ninox -process=1,1 ...
#
# with all other switches identical. The first invocation here will process
# images 0,2,4,6,... and the second will process images 1,3,5,7 ...
global int ProcessOffset = 0;
global int ProcessSkip = 0;

	// process=Offset,Skip for cooperatively processing a video sequence
	if (sscanf(VALUE_S,"%d,%d",&ProcessOffset,&ProcessSkip) != 2) {
	   printf("process: invalid argument, wanted process=offset,skip\n");
	   FAIL;
	   }

	if (ProcessSkip) {
	   //Print("Set CPU affinity to %d\n",ProcessOffset); fflush(stdout);
	   SetProcessorAffinity(ProcessOffset);
	   }

edgemargin em
#
# The image centre must be at least this far in from any edge
# for the image to be used. Otherwise it's considered broken.
# Default is 20 pixels
global int EdgeMargin = 20;

	// Image centre must be at least this far in from any edge
	// for the image to be used
	EdgeMargin = VALUE_I;

scan
global int Sub_x = 0;
global int Sub_y = 0;

	// give a non-central place to start scanning for FindCentre()
	int x1,y1;
	char fname[512];

	if (sscanf(VALUE_S,"%d,%d",&x1,&y1) != 2) {
	   printf("-scan: invalid format, should be -scan=x,y\n");
	   FAIL;
	   }

	Sub_x = x1;
	Sub_y = y1;

tracking
# tracking=N will cause ninox to remember the location of the image centre from
# one frame to the next and search a box that is NxN, centered on the previous image
# centre. This helps ninox to ignore transient objects that come in from the edge of screen
# (eg moons or stars).
# On the first frame the entire image is searched to locate the centre, unless you set
# the initial location with Sub_x and Sub_y
global int ImageTracking = 0;

	ImageTracking = VALUE_I;
	if (ImageTracking < 10) {
	   printf("Error: tracking=%d: tracking box %dx%d not large enough\n",
		ImageTracking,ImageTracking,ImageTracking);
	   FAIL;
	   }

showmovement
# showmovement=true will caluse ninox to print the distance between barycentres between
# each frame as it is processed. imagetracking must be enabled for this option to work.
global int ShowMovement = 0;

	BOOLEAN(ShowMovement);

maxallowedmovement
# maxallowedmovement=N instructs ninox to drop any frames where the barycentre shifts by
# N pixels or more between successive frames. Requires imagetracking to be enabled.
global int MaxAllowedMovement = 0;

	MaxAllowedMovement = VALUE_I;

centrefunc
# Determines how ninox finds the centre of an image to be cut out. The default
# (ie if centrefunc is omitted) is to find the centre of brightness of the whole image
# known as the barycentre.
#
# If you give centrefunc=left then this process is modified to only count the "first" 
# object found in the frame (scanning left -> right).
#
# If you give centrefunc=right then the process is modified to only count the "first"
# object found in the frame scanning from right -> left.
#
# The "left" and "right" modes are useful when you want to lock onto one particular object
# in a frame that contains other objects that are moving relative to the one you want, and
# you want to ignore everything other than the first object (looking from either left or right).
#
global int CentreMode = CENTRE_BARYCENTRE;

	if (! strcasecmp(VALUE_S,"left"))  CentreMode = CENTRE_LEFT;
	if (! strcasecmp(VALUE_S,"right")) CentreMode = CENTRE_RIGHT;

subregion
global int EnableSubRegion = 0;
global int SR_X1 = 0;
global int SR_Y1 = 0;
global int SR_X2 = 0;
global int SR_Y2 = 0;

	// subregion specifies a region to be used after the image has been centered
	// and cutout. This subregion replaces the centered image
	int x1,y1,x2,y2;
	char fname[512];

	if (sscanf(val,"%d,%d,%d,%d",&x1,&y1,&x2,&y2) != 4) {
	   printf("-subregion: Invalid format, should be -subregion=x1,y1,x2,y2\n");
	   FAIL;
	   }

	EnableSubRegion = 1;
	SR_X1 = x1;
	SR_X2 = x2;
	SR_Y1 = y1;
	SR_Y2 = y2;

	DoCutout=1;

levels
# Applies a histogram stretch to each frame, setting the black and white levels according
# to the values given in the -levels=MIN,MAX argument. MIN and MAX are given as 8 bit values
# and will be automatically converted to 16 bit if required.
global int LevelsMin = 0;
global int LevelsMax = 0;
global int doLevelsAdjust = 0;

	int min,max;
	if (sscanf(val,"%d,%d",&min,&max) != 2) {
	   printf("syntax error on 'levels'\n");
	   FAIL;
	   }

	LevelsMin = min;
	LevelsMax = max;
	doLevelsAdjust = 1;

dbf-diff
global int DBF_DIFF = 15;

	DBF_DIFF = VALUE_I;

count maxcount
# Maximum number of images to generate
global int ImageCount= IMAGECOUNT_MAX;
	// How many images to process
	ImageCount=VALUE_I;

max-per-channel
# Max number of frames per channel 
# ie per SER or FTA data archive
global int MaxFramesPerChannel = -1;
	MaxFramesPerChannel = VALUE_I;

nocutout
	DoCutout=0;
	ForceProcess = 1;

# Flip the image horizontally
mirror hinvert flipx
global int doMirrorImage = 0;
	BOOLEAN(doMirrorImage);

# Flip the image vertically
invert flipy
global int doInvertImage = 0;
	BOOLEAN(doInvertImage);

# Flip every second image vertically
invert2 flipy2
global int doInvertImage2 = 0;
	BOOLEAN(doInvertImage2);

forceprocess
global int ForceProcess = 0;

	BOOLEAN(ForceProcess);

protect
global int HistoProtect = 1;
	BOOLEAN(HistoProtect);

stretch
global int HistoStretch = 0;
	HistoStretch = VALUE_I;
	if (HistoStretch==0) HistoStretch = 200;

istretch mstretch
# Stretches the input image to have the brightest pixel at 80% (default, can be changed with the mstretch option)
#
global int InputHistoStretch = -1;
global double MAX_STRETCH = 0.85;
	BOOLEAN(InputHistoStretch);
	if (VALUE_F > 0 && VALUE_F < 1) {
	   MAX_STRETCH = VALUE_F;
	   InputHistoStretch = 1;
	   }

quiet
global int Quiet = 0;
	BOOLEAN(Quiet);
	if (Quiet) PrintFlush = 1;

debayer
global int DoDeBayer = 0;
	if (VALUE_I) DoDeBayer = VALUE_I;
	else {
	   if (! strcasecmp(VALUE_S,"gbg")) DoDeBayer=1;
	   if (! strcasecmp(VALUE_S,"rgr")) DoDeBayer=2;
	   if (! strcasecmp(VALUE_S,"bgb")) DoDeBayer=3;
	   if (! strcasecmp(VALUE_S,"grg")) DoDeBayer=4;
	   }

bayer_x
global int BayerX = 1;
	BayerX = VALUE_I;

bayer_y
global int BayerY = 1;
	BayerY = VALUE_I;

bayer8
global int Bayer8 = 0;
	BOOLEAN(Bayer8);

xoffset
global int CutX_Offset = 0;
	CutX_Offset = VALUE_I;

yoffset
global int CutY_Offset = 0;
	CutY_Offset = VALUE_I;

cutx
global int CutX = 360;
	CutX = VALUE_I;
	if (CutX < 100) {
	   Print("-cutx: minimum supported size 100 pixels\n");
	   FAIL;
	   }

cuty
global int CutY = 360;
	CutY = VALUE_I;
	if (CutY < 100) {
	   Print("-cutx: minimum supported size 100 pixels\n");
	   FAIL;
	   }

cut
global int newWidth = -1;
global int newHeight = -1;

	int x,y;

	if (sscanf(VALUE_S,"%d,%d",&x,&y)==2) {
		CutX = x;
		CutY = y;
	   	newWidth = CutX;
	   	newHeight = CutY;
		}
	else if (sscanf(VALUE_S,"%dx%d",&x,&y)==2) {
		CutX = x;
		CutY = y;
	   	newWidth = CutX;
	   	newHeight = CutY;
		}
	else {
	   if (VALUE_I < 100 || VALUE_I>10000) {
		Print("%d: Outside valid bounds 100..10000\n",VALUE_I);
		FAIL;
		}
	   CutX = CutY = VALUE_I;

	   newWidth = CutX;
	   newHeight = CutY;
	   }

white
global int White = 255;
	White = VALUE_I;

upscale upsample resample qresample rescale
global int UpScale = 1;
global int DownScale = 1;

	char *ptr = strstr(VALUE_S,"/");
	int up=1,down=1;

	if (ptr) {
	   *ptr = 0;
	   down = atoi(ptr+1);
	   }

	up = VALUE_I;
	if (up < 1 || up > 6 || down<1 || down>10) {
	   Print("UpScale: %d/%d out of accepted range (1..6)/(1..10) \n",up,down);
	   FAIL;
	   }
	UpScale = up;
	DownScale = down;

	// Make sure the qestimator is on. If the user has not set a preference
	// then keep the original order
	if (! QEstimator) {
	   QEstimator = 1;
	   QRenumberFiles = 1;
	   QualityFunction = QF_NONE;
	   }

popfilter
global int PopFilter_When = POPFILTER_BEFORE;
global int PopFilter_PassCount = 0;
global int PopFilter = 0;

 	char when[32];
	int count,sz = 7;

	++PopFilter_PassCount;

	if (sscanf(VALUE_S,"%d,%d",&sz,&count) == 2) {
	   PopFilter_PassCount = count;
	   }
	else if (sscanf(VALUE_S,"%d,%s",&sz,when) == 2) {
	   if (!strcasecmp(when,"before")) PopFilter_When = POPFILTER_BEFORE;
	   else if (!strcasecmp(when,"after")) PopFilter_When = POPFILTER_AFTER;
	   else {
		Print("Unknown option '%s' to popfilter\n",when);
		FAIL;
		}
	   if (sz<=0) {
		Print("invalid size arg '%d' to popfilter\n",sz);
		FAIL;
		}
	   PopFilter = sz;
	   }
	else if (! strcasecmp(VALUE_S,"before")) {
		PopFilter_When = POPFILTER_BEFORE;
		PopFilter = sz;
		}
	else if (! strcasecmp(VALUE_S,"after")) {
		PopFilter_When = POPFILTER_AFTER;
		PopFilter = sz;
		}
	else if (VALUE_I > 0) {
		PopFilter = VALUE_I;
		PopFilter_When = POPFILTER_BEFORE;
		}
	else {
		PopFilter = sz;
		PopFilter_When = POPFILTER_BEFORE;
		}

vpop
# Enable popfilter visualisation - each tile that changes
# due to a bright or dark pixel will light up.
# Use for testing and debugging only
global int VisualisePopFilter = 0;
	BOOLEAN(VisualisePopFilter);

gain
global double Gain = 1.0;
global int ChangeGain = 0;

	Gain = VALUE_F;
	if (Gain != 1.0) ChangeGain = 1;

gamma
global double Gamma = 1.0;
global int ChangeGamma = 0;

	double g = VALUE_F;
	if (g < 0.3 || g > 2.5) {
	   Print("Gamma value %f is outside accepted range 0.3 <= gamma <= 1.5\n",g);
	   FAIL;
	   }
	Gamma = g;
	if (Gamma != 1.0) ChangeGamma = 1;

streamfilter
global int StreamFilter = 0;
global int StreamFilter_History = 5;

	StreamFilter = 1;
	if (VALUE_I)
	   StreamFilter_History = VALUE_I;

bayerboost
global double BayerBoost = 1.0;
	BayerBoost = VALUE_F;
	if (BayerBoost == 0) BayerBoost = 1.0;

overwrite
global int AllowOverwrite = 0;
	BOOLEAN(AllowOverwrite);

outdir
global char *OutDir = NULL;
	int i;
	Strcpy(OutDir,VALUE_S);
	for(i=0; OutDir[i]; ++i) if (OutDir[i]=='\\') OutDir[i]='/';

	if (MkDirs) {
	   char *strtmp = Strdup(OutDir);
	   char *ptr = strchr(strtmp,'/');

	   while(ptr) {
		*ptr = 0;
		if (! isDirectory(strtmp)) {
		   Print("Creating directory '%s'\n",strtmp);
		   Mkdir(strtmp);

		   if (! isDirectory(strtmp)) {
			Print("Mkdir on '%s' failed, cannot continue\n");
			exit(0);
			}
		   }
		else {
		   Print("Directory '%s' exists\n",strtmp);
		   }

		*ptr = '/';
		ptr = strchr(ptr+1,'/');
		}
	   Free(strtmp);
	   }

OutDirAppendSrc OutDir-Append-Src OutDir_Append_Src
global int OutDirAppendSrc = 0;
	BOOLEAN(OutDirAppendSrc);

OutDirPrependSrc OutDir-Prepend-Src OutDir_Prepend_Src
global int OutDirPrependSrc = 0;
	BOOLEAN(OutDirPrependSrc);

save
	BOOLEAN(NoSave);
	NoSave = 1 - NoSave;

nosave
global int NoSave = 0;
	BOOLEAN(NoSave);

threshhold
global int ThreshHold = 40;

	int t = VALUE_I;
	if (t<5 || t > 250) {
	   Print("Threshhold %d out of allowed range 5..250\n",t);
	   FAIL;
	   }
	ThreshHold = t;

minpixels
global int MinPixels = 250;
	MinPixels = VALUE_I;
	if (MinPixels<5 || MinPixels > 10000) {
	   Print("Minpixels: %d out of allowed range 10..10000\n",MinPixels);
	   FAIL;
	   }

width
	newWidth = VALUE_I;
	if (newWidth < 100 || newWidth>10000) {
	   Print("Width must be 100..10000\n");
	   FAIL;
	   }

height
	newHeight = VALUE_I;
	if (newHeight < 100 || newHeight > 10000) {
	   Print("Height must be 100..10000\n");
	   FAIL;
	   }

stack stackfile
global char *StackFile = NULL;
	StackFile = Strdup(VALUE_S);
	ForceProcess=1;
	DoCutout=0;
	NoSave=1;
	InputHistoStretch = 0;

stackcount
global int StackMax = 100000;
	StackMax = VALUE_I;

mergefile
global char * MergeFile = NULL;
	MergeFile = Strdup(VALUE_S);

mergethreshhold
global double MergeThreshHold = 0.2;

	MergeThreshHold = VALUE_F;

writeemptyfiles
global int forceWriteEmptyFiles = 0;
global int WriteEmptyFiles = 1;

	BOOLEAN(forceWriteEmptyFiles);
	BOOLEAN(WriteEmptyFiles);

Observer obs
# Set the Winjupos Obsever name
global char *Observer = NULL;
	Observer = Strdup(VALUE_S);

loadqfiles qfiles
# Load back quality sorted info from the quality-X-Y.txt files
# stored in the .ninox directory from a previous set of runs
# and jump straight to the quality sort.
#
# Rely on -process=start,skip to tell us the total number
# of quality files present... "skip" is the total, so they will
# be named quality-0-S  quality-1-S  quality-2-S ... quality-(S-1)-S
global int DoLoadQFiles = 0;
	DoLoadQFiles = 1;

// Assign quality anyway, even if image contains overbright pixels
qf_ignorehistowarning
global int IGNORE_HISTO_WARNING = 0;
	BOOLEAN(IGNORE_HISTO_WARNING);

minq minquality
# Only keep frames that reach this quality
global double MinQuality = 0;

	MinQuality = VALUE_F;

qestimator qe
global int QEstimator = 0;
global int QSUBSAMPLE_MIN = 3;
global int QSUBSAMPLE_MAX = 5;
global int QSUBSAMPLE_INC = 1;

	int min,max,ch,step;
	BOOLEAN(QEstimator);

	// Possible value "n,m" representing min and max subsampling
	// or "n,m,i" for sampling between n and m with increment i

	if (sscanf(val,"%d%c%d%c%d",&min,&ch,&max,&ch,&step)==5) {
	   QSUBSAMPLE_MIN = min;
	   QSUBSAMPLE_MAX = max;
	   QSUBSAMPLE_INC = step;
	   QEstimator = 1;
	   }
	else if (sscanf(val,"%d%c%d",&min,&ch,&max)==3) {
	   QSUBSAMPLE_MIN = min;
	   QSUBSAMPLE_MAX = max;
	   QEstimator = 1;
	   }
	else if (sscanf(val,"%d",&min)==1) {
	   QSUBSAMPLE_MIN = min;
	   QSUBSAMPLE_MAX = min;
	   QEstimator = 1;
	   }

qprefix qpr
global char *QPR = "q";

	QPR = Strdup(VALUE_S);

qcount qc
global int QualityMaxCount = 0;

	QualityMaxCount = VALUE_I;

qfunc qf maxq
global int QualityFunction = GRADIENT;
global int QF_APERTURE_RADIUS = 0;

	if (! strncasecmp(VALUE_S,"hist",4)) QualityFunction = HISTO;
	else if (! strncasecmp(VALUE_S,"none",4))   QualityFunction = QF_NONE; // Keep original order
	else if (! strncasecmp(VALUE_S,"grad",4))   QualityFunction = GRADIENT; // default
	else if (! strncasecmp(VALUE_S,"thresh",4)) QualityFunction = BR_THRESH;
	else if (! strncasecmp(VALUE_S,"+thresh",4)) QualityFunction = ADD_BR_THRESH;
	else if (! strncasecmp(VALUE_S,"mincount",4)) QualityFunction = QF_MINCOUNT;
	else if (! strncasecmp(VALUE_S,"sosq",4)) QualityFunction = QF_SOSQ;
	else if (! strncasecmp(VALUE_S,"random",6)) QualityFunction = QF_RANDOM;
	else if (! strncasecmp(VALUE_S,"aperture:",9)) {
		int r = atoi(VALUE_S+9);
		if (r>0) QF_APERTURE_RADIUS = r;
		else {
		   Print("QF_Aperture: No radius given, format is qfunc=aperture:R\n");
		   exit(1);
		   }
		QualityFunction = QF_APERTURE;
		}
	else {
		printf("Unknown quality function '%s'\n",val);
		FAIL;
		}
	// Set defaults if we haven't given them on the commandline
	QEstimator = 1;
	if (! QRenumberFiles) QRenumberFiles = 1;

qmargin
global double QMargin = 0.1;

	   // -qmargin=0.25 for 25% margin on all borders
	   QMargin = VALUE_F;

qhistfile
global char *QHISTO_FILE = ".ninox/histo.txt";
	   QHISTO_FILE = Strdup(VALUE_S);

qhisto
global int QHISTO_MIN = -1;
global int QHISTO_MAX = -1;

	int x1,y1,x2,y2,min,max;
	if (sscanf(val,"%d,%d-%d,%d:%d-%d",&x1,&y1,&x2,&y2,&min,&max)==6)
		QAddRegion(x1,y1,x2,y2,min,max);
	if (sscanf(val,"%d,%d",&min,&max)==2) {
	   	QHISTO_MIN = min;
		QHISTO_MAX = max;
		}
	else {
		printf("Usage: -qhisto=min,max (8 bit values)\n");
		FAIL;
		}

qtrim trim
global int QTrim_Before = 1;
global int QTrim_After = 1;
global int QTrim_Type = QTRIM_SECONDS;

        int b,a;
        // Check for time in seconds
        if (sscanf(val,"%ds,%ds",&b,&a) == 2) {
           if (b>0) QTrim_Before = b;
           if (a>0) QTrim_After = a;
           QTrim_Type = QTRIM_SECONDS;
           }
        else if (sscanf(val,"%ds",&a) == 1) {
	   if (a>0) QTrim_Before = QTrim_After = a;
           QTrim_Type = QTRIM_SECONDS;
           }
        // check for qtrim in frames
        else if (sscanf(val,"%d,%d",&b,&a) == 2) {
           if (b>0) QTrim_Before = b;
           if (a>0) QTrim_After = a;
           QTrim_Type = QTRIM_FRAMES;
           }
        else if (sscanf(val,"%d",&a) == 1) {
	   if (a>0) QTrim_Before = QTrim_After = a;
           QTrim_Type = QTRIM_FRAMES;
	   }
        else {
	   int b;
	   BOOLEAN(b);
	   if (! b) QTrim_Before = QTrim_After = 0;
           }

	Print("Trim: %d%s , %d%s\n",
		QTrim_Before,QTrim_Type==QTRIM_SECONDS?"s":"",
		QTrim_After,QTrim_Type==QTRIM_SECONDS?"s":""
		);

dorenumber

	AllowOverwrite=1;
	QRenumber(QRenumberFiles);

renumber qrenumber qsort
global int QRenumberFiles = 0;
global int QRenumberPreserveOrder = 0;

	int a;

	BOOLEAN(QRenumberFiles);
	if (!strcasecmp(VALUE_S,"last")) 
		QRenumberFiles = QRENUMBER_LAST;

	// qrenumber=preserve,50 means to keep the original
	// file order and keep the best 50%. Files lower than
	// the cutoff are deleted.
	if (sscanf(VALUE_S,"preserve,%d",&a) == 1)
		QRenumberPreserveOrder = a;

qwrite
global int QWriteIntermediateFiles = 0;
	BOOLEAN(QWriteIntermediateFiles);

hnoisefilter
global int HNoiseFilter = 0;
	BOOLEAN(HNoiseFilter);

subdirs
global int SubDirs = 1;
	BOOLEAN(SubDirs);

default-config
	ShowGlobals();
	exit(1);

debug
global int DEBUG=0;

	BOOLEAN(DEBUG);
	if (DEBUG) PrintFlush=1;

morphing
global int Morphing = 0;
	Morphing = atoi(val);

morph-ref morph_ref mr
global char *Morph_Ref = NULL;

	Morph_Ref = Strdup(VALUE_S);
	if (access(Morph_Ref,0)) {
	   Print("morph_ref: Reference image '%s' does not exist\n",val);
	   FAIL;
	   }

morph-across morph_across ma
global int Morph_Across = -1;
	// Number of control points across the image
	Morph_Across = VALUE_I;

morph-down morph_down md
global int Morph_Down = -1;
	// Number of control points down the image
	Morph_Down = VALUE_I;
	

morph-it morph_it mi morph-iterations morph_iterations
global int Morph_Iterations = 1;
	// Number of morph iterations to run
	Morph_Iterations = VALUE_I;

morph-dr morph-drift morph_drift
global int Morph_Drift = 2;
	Morph_Drift = VALUE_I;

morph-max-translate mx morph_max_translate
global int Morph_MaxTranslate = 100;
	Morph_MaxTranslate = VALUE_I;

morph-de mdebug mdbg morph_debug
global int Morph_Debug = 0;
	// Enable extra debugging
	Morph_Debug = VALUE_I;
	printf("[%d]\n",Morph_Debug);

morph-min-divisor morph_min_divisor
global int Morph_Min_Divisor = 5;
	Morph_Min_Divisor = VALUE_I;
	
input-type ift
global int InputFileType = -1;

	if (!strcasecmp(VALUE_S,"bmp")) 	InputFileType = IMG_BMP;
	else if (!strncasecmp(VALUE_S,"fit",3)) InputFileType = IMG_FIT;
	else if (!strcasecmp(VALUE_S,"ppm")) 	InputFileType = IMG_PPM;
	else {
	   Print("input file type '%s' not understood\n",VALUE_S);
	   FAIL;
	   }

output-type oft
global int OutputFileType = -1;

	if (!strcasecmp(VALUE_S,"bmp")) 	OutputFileType = IMG_BMP;
	else if (!strncasecmp(VALUE_S,"fit",3)) OutputFileType = IMG_FIT;
	else if (!strcasecmp(VALUE_S,"ppm")) 	OutputFileType = IMG_PPM;
	else {
	   Print("output file type '%s' not understood\n",VALUE_S);
	   FAIL;
	   }

output-stack-depth osd
global int OutputStackFileDepth = -32;
	OutputStackFileDepth = VALUE_I;

output-depth od
global int OutputFileDepth = -1;
	OutputFileDepth = VALUE_I;
	if (OutputFileType < 0) OutputFileType = IMG_FIT;

rotate
global double RotateAngle = 0;
global int do_Rotate=0;

	RotateAngle = VALUE_F;
	if (RotateAngle != 0) do_Rotate=1;

rotatecutoff
global int RotateCutoff = 45;

	RotateCutoff = VALUE_I;

display
global int DisplayFrames = 0;
global int DisplayPause=0;
	BOOLEAN(DisplayFrames);
	if (DisplayFrames && ! strcasecmp(VALUE_S,"pause")) DisplayPause=1;

darkframe
global int DoDarkFrameSubtract = 0;

         BOOLEAN(DoDarkFrameSubtract);

         if (DoDarkFrameSubtract && ! LoadDarkFrameRef(VALUE_S)) {
               Print("Error loading dark frame reference '%s'\n",VALUE_S);
               FAIL;
               }
	if (DoInvertDarkFrame)
	   if (InvertDarkFrame()) DoInvertDarkFrame=0;

invertdarkframe dinvert
global int DoInvertDarkFrame = 0;
	BOOLEAN(DoInvertDarkFrame);
	if (DoInvertDarkFrame) 
	   if (InvertDarkFrame()) DoInvertDarkFrame=0;

gaincomp
global int ApplyGainComp = 0;

         BOOLEAN(ApplyGainComp);

         if (ApplyGainComp && ! LoadGainCompRef(VALUE_S)) {
               Print("Error loading gain compensation reference\n");
               FAIL;
               }

gc_deadcol gcdeadcol
global int GaincompDeadColCount=0;
global int GaincompDeadColList[32];
	GaincompDeadColList[GaincompDeadColCount++] = VALUE_I;

gcboost
global double GaincompBoost = 1.0;
	GaincompBoost = VALUE_F;

gcderingdelay
global int GaincompDeRingDelay = 0;
	GaincompDeRingDelay = VALUE_I;

gcderingamount
global double GaincompDeRingAmount = 0;
	GaincompDeRingAmount = VALUE_F;

gcderingamount2
global double GaincompDeRingAmount2 = 0.0;
	GaincompDeRingAmount2 = VALUE_F;

bias
# bias is an amount to add onto pixels when we load the image.
# It should be given as an 8 bit value (0-255) and it will
# be scaled automatically to other depths
global int Bias = 0;
	Bias = VALUE_I;
	if (Bias>0) AutoBlack=0;

subsamplemode
global int SubsampleMode = 0;

       SubsampleMode = VALUE_I;
       NoSave = 1;

subsample_pixel_width
global int Subsample_pixel_width = 10;
	Subsample_pixel_width = VALUE_I;

subsample_pixel_height
global int Subsample_pixel_height = 10;
	Subsample_pixel_height = VALUE_I;

subsample_pixel_size
	Subsample_pixel_height = Subsample_pixel_width = VALUE_I;

subsample_xpixels
global int Subsample_xpixels= 640;
	Subsample_xpixels= VALUE_I;

Subsample_ypixels
global int Subsample_ypixels= 480;
	Subsample_ypixels= VALUE_I;

subsample_nframes
global int Subsample_nframes = 100;
	Subsample_nframes = VALUE_I;

subsample_min_offset
global double Subsample_min_offset = 0;
	Subsample_min_offset = VALUE_F;

subsample_max_offset
global double Subsample_max_offset = 5;
	Subsample_max_offset = VALUE_F;

subsample_upscale
global int Subsample_upscale = 0;
	Subsample_upscale = VALUE_I;

subsample_noise
global int Subsample_noise = 0;
	Subsample_noise = VALUE_I;

subsample_scan
global int Subsample_scan = 0;
	BOOLEAN(Subsample_scan);

subsample_prefix
global char *Subsample_prefix = "sample_";
	Subsample_prefix = Strdup(VALUE_S);

runfile
global char *RunFile = NULL;
	RunFile = Strdup(VALUE_S);

iquant
global int IQuant = 0;
	IQuant = VALUE_I;

rowbalance
global int doAutoRowBalance = 0;
	BOOLEAN(doAutoRowBalance);

rowboost
# Correct for odd/even row gain imbalances
# rowboost=odd,x means boost odd rows by x (floating point) ie pixel *= x
# rowboost=even,x means boost even rows by x (floating point) ie pixel *= x
global int doRowBoost = 0;
global int RowBoostType = 0;  // RB_EVEN_MULT RB_ODD_MULT RB_EVEN_ADD RB_ODD_ADD
global double RowBoostMult = 1.0;   // For multiplication
global double RowBoostAdd = 0;      // For addition
	double v;

	if (sscanf(VALUE_S,"even,*%lf",&v)==1) {
		doRowBoost=1;
		RowBoostType = RB_EVEN_MULT;
		RowBoostMult = v;
		RowBoostAdd = 0;
		Print("rowboost: even rows: *= %lf\n",v);
		}
	else if (sscanf(VALUE_S,"even,+%lf",&v)==1) {
		doRowBoost=1;
		RowBoostType = RB_EVEN_ADD;
		RowBoostMult = 1.0;
		RowBoostAdd = v;
		Print("rowboost: even rows: += %lf\n",v);
		}
	else if (sscanf(VALUE_S,"odd,*%lf",&v)==1) {
		doRowBoost=1;
		RowBoostType = RB_ODD_MULT;
		RowBoostMult = v;
		RowBoostAdd = 0;
		Print("rowboost: odd rows: *= %lf\n",v);
		}
	else if (sscanf(VALUE_S,"odd,+%lf",&v)==1) {
		doRowBoost=1;
		RowBoostType = RB_ODD_ADD;
		RowBoostMult = 1.0;
		RowBoostAdd = v;
		Print("rowboost: odd rows: += %lf\n",v);
		}
	else {
	   Print("rowboost '%s' not understood\n",VALUE_S);
	   exit(1);
	   }

rowcorrection rc
# Attempt to correct per-row gain inconsistencies by generating a
# per-row correction to bring it back to the average
global int RowCorrection = 0;
	BOOLEAN(RowCorrection);
	if (VALUE_I > 1) RowCorrection = VALUE_I;

columncorrection cc
# Attempt to correct per-column gain inconsistencies by generating a
# per-column correction to bring it back to the average
global int ColumnCorrection = 0;
	BOOLEAN(ColumnCorrection);
	if (VALUE_I > 1) ColumnCorrection = VALUE_I;

fixrowmissing fxr
# Fix the issue of missing horizontal strips of data caused by usb errors
global int doFixRowMissing = 0;
	BOOLEAN(doFixRowMissing);

autoblack ablack
# Automatically detect black level in images by scanning top and
# bottom rows, subtract average value from all pixels
global int AutoBlack = 0;
global double AutoBlackAmount = 0.9;
	BOOLEAN(AutoBlack);
	if (strchr(VALUE_S,'.')) {
	   AutoBlack = 1;
	   AutoBlackAmount = VALUE_F;
	   }

zblack
# Used when decompressing .ftz archives, sets the background default
# pixel brightness. range 0..255
global int ZBlack = 0;
	ZBlack = VALUE_I;

mkdirs
# Usage: -mkdirs=yes
# Creates all parent directories for the -outdir directory
global int MkDirs = 0;
	BOOLEAN(MkDirs);

hist
# Usage: -hist  show histogram of image
global int ShowHistogram = 0;
	BOOLEAN(ShowHistogram);

histo-warning overbright ob
# Usage: -histo-warning=off  OR  -nohisto-warning
global int EnableHistoWarning = 1;
	BOOLEAN(EnableHistoWarning);

grey
# Converts 24bpp input into greyscale (16 bpp if possible)
global int Grey = 0;
	BOOLEAN(Grey);

grey8
# Converts input into 8bpp greyscale
global int Grey8 = 0;
	BOOLEAN(Grey8);
	Grey = Grey8;

bmp-ignore-offset
# Ignore broken BMP header H.offset
global int BmpIgnoreOffset = 0;
	BOOLEAN(BmpIgnoreOffset);

stdin
# Read frames from standard input
# default to 16 bit .fit for output
global int UsingStdin = 0;
	UsingStdin = 1;
	if (OutputFileType<0) OutputFileType = IMG_FIT;
	if (OutputFileDepth<0) OutputFileDepth = 16;

stdout
# Write frames to stdout
# Also selects stderr for all normal Print() messages
global int UsingStdout = 0;
	UsingStdout = 1;
	NoSave = 1;
	Select(stderr);

write-ser
# Write the finished frames to the named ser file
global int WriteSer = 0;
global char *WriteSerFile = NULL;
global FILE * WriteSerHandle = NULL;
	WriteSer = 1;
	WriteSerFile = Strdup(VALUE_S);

write-ser-frames
# write-ser-frames=N
# Create multiple ser files, each one containing N frames
# and append a unique suffix to each filename
global int WriteSerFramecount = 0;
	WriteSerFramecount = VALUE_I;

qwrite-ser
# Write the quality-sorted frames into an SER archive. Formats are:
# 	qwrite-ser = fname
#
#	qwrite-ser = channel:fname, channel:fname ...
#
#  *NOTE If "fname" starts with "WJ-" then the winjupos prefix
#        "YYYY-MM-DD-HHMM_S-OBS-" will be used
global int QWriteSER = 0;
global char *SER_Filename_map[NUM_CHANNELS];
global char *QWriteSER_fname = NULL;
	QWriteSER = 1;
	char *str = Strdup(VALUE_S);
	if (strstr(str,":")) while (str = strstr(str,":")) {
	   int ch = *(str-1) - '0';
	   if (*(str-2)>='0' && *(str-2)<='9') { ch += (*(str-2) - '0')*10; }
	   str++;
	   if (ch<1 || ch>MAX_CHANNEL) { Print("Channel '%d' out of bounds 1..%d\n",ch,MAX_CHANNEL); exit(1); }
	   SER_Filename_map[ch] = Malloc(1024);
	   char *dst = SER_Filename_map[ch];
	   while(*str && *str != ',') { *(dst++) = *(str++); } *dst = 0;
	   //Print("Mapped [%s] on channel %d\n",SER_Filename_map[ch],ch);
	   }
	else if (strlen(VALUE_S)) QWriteSER_fname = Strdup(VALUE_S);

qwrite-ser-count
# The count of how many frames to write into the qwrite-ser file
# Default is to write all frames
global int QWriteSER_count = -1;
global double QWriteSER_fcount = -1.0;
	if (VALUE_F > 0 && VALUE_F < 1) QWriteSER_fcount = VALUE_F;
	else QWriteSER_count = VALUE_I;
	//Print("QWriteSER counts: fractional=%lf absolute=%d\n",QWriteSER_fcount,QWriteSER_count);

qwrite-ser-delete-old
# If set then ninox deletes the original .fit source files after adding them into
# a .ser archive
global int QWriteSER_DeleteOld = 1;
	BOOLEAN(QWriteSER_DeleteOld);
	
mask
# Apply this mask to the raw data, given as a 16bpp value
# eg mask=0xffff for no-op or mask=0xff to get low 8 bits
global unsigned int Mask = 0;
	Mask = VALUE_I;
	Print("Set mask to %04x\n",Mask);
	fflush(stdout);

printflush
# Flush stdout after every print - boolean, default = NO
global int PrintFlush=0;
	BOOLEAN(PrintFlush);

noprint
# Do not generate any stdout data
global int NoPrint = 0;
	BOOLEAN(NoPrint);

tmpdir
# Store intermediate file data here (should be ramdisk or ssd)
global char * TmpDir = NULL;
	TmpDir = Strdup(VALUE_S);

	if (! isDirectory(TmpDir)) {
	   Print("Warning: Given tmpdir '%s' doesn't exist, caching disabled\n",TmpDir);
	   TmpDir = NULL;
	   }

