/*
 *	spatialAnalysis.cpp - Perform various data analysis on 3D point clouds
 *	Copyright (C) 2018, D Haley 

 *	This program is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.

 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.

 *	You should have received a copy of the GNU General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <gsl/gsl_sf_gamma.h>

#include "spatialAnalysis.h"



#include "algorithms/spatial.h"
#include "geometryHelpers.h"
#include "filterCommon.h"
#include "algorithms/binomial.h"
#include "algorithms/K3DTree-mk2.h"
#include "backend/plot.h"
#include "../APT/APTFileIO.h"
#include "common/colourmap.h"
#include "wx/wxcommon.h"

using std::vector;
using std::set;
using std::string;
using std::pair;
using std::make_pair;
using std::map;
using std::list;

enum
{
	KEY_STOPMODE,
	KEY_ALGORITHM,
	KEY_DISTMAX,
	KEY_NNMAX,
	KEY_NNMAX_NORMALISE,
	KEY_NNMAX_SHOWRANDOM,
	KEY_NUMBINS,
	KEY_REMOVAL,
	KEY_NORMALISERDF,
	KEY_REDUCTIONDIST,
	KEY_RETAIN_UPPER,
	KEY_CUTOFF,
	KEY_COLOUR,
	KEY_ENABLE_SOURCE_ALL,
	KEY_ENABLE_TARGET_ALL,
	KEY_ENABLE_NUMERATOR_ALL,
	KEY_ENABLE_DENOMINATOR_ALL,
	KEY_ORIGIN,
	KEY_NORMAL,
	KEY_RADIUS,
	KEY_NUMIONS,
	KEY_SHOW_BINOM_FREQ,
	KEY_SHOW_BINOM_NORM_FREQ,
	KEY_SHOW_BINOM_THEOR_FREQ,
	KEY_SHOW_BINOM_3D_GRID,
	KEY_BINOMIAL_MAX_ASPECT,
	KEY_BINOMIAL_EXTRUDE_DIR,
	KEY_REPLACE_FILE,
	KEY_REPLACE_TOLERANCE,
	KEY_REPLACE_ALGORITHM,
	KEY_REPLACE_VALUE,
	KEY_ANGULARSTEP,
	KEY_SHOW_DF_LOGSCALE,
	KEY_DF_COLOURMAP,
};

enum 
{ 
	KEYTYPE_ENABLE_SOURCE=1,
	KEYTYPE_ENABLE_TARGET,
	KEYTYPE_ENABLE_NUMERATOR,
	KEYTYPE_ENABLE_DENOMINATOR,
};

enum {
	ALGORITHM_DENSITY, //Local density analysis
	ALGORITHM_DENSITY_FILTER, //Local density filtering
	ALGORITHM_RDF, //Radial Distribution Function
	ALGORITHM_AXIAL_DF, //Axial Distribution Function (aka atomvicinity, sdm, 1D rdf)
	ALGORITHM_BINOMIAL, //Binomial block method for statistical randomness testing
	ALGORITHM_REPLACE, //Remove, set or modify points using an external file
	ALGORITHM_LOCAL_CHEMISTRY, //Obtain a local chemistry plot, as described by Hyde and Marquis (10.1016/j.mser.2010.05.001)
	ALGORITHM_LOCAL_CHEMISTRY_FILTER, //Filter points based on local concentration
	ALGORITHM_AXIAL_DF_SWEEP, //Perform a 2D sweep of axial distribution functions 
	ALGORITHM_ENUM_END,
};

enum{
	STOP_MODE_NEIGHBOUR,
	STOP_MODE_RADIUS,
	STOP_MODE_ENUM_END
};

enum
{
	REPLACE_MODE_SUBTRACT,
	REPLACE_MODE_INTERSECT,
	REPLACE_MODE_UNION,
	REPLACE_MODE_ENUM_END
};

//!Error codes
enum
{
	ERR_ABORT_FAIL=1,
	ERR_BINOMIAL_NO_MEM,
	ERR_NO_RANGE,
	ERR_BINOMIAL_BIN_FAIL,
	INSUFFICIENT_SIZE_ERR,
	ERR_FILE_READ_FAIL,
	SPAT_ERR_END_OF_ENUM,
};
// == NN analysis filter ==


//User visible names for the different algorithms
const char *SPATIAL_ALGORITHMS[] = {
	NTRANS("Local Density"),
	NTRANS("Density Filtering"),
	NTRANS("Radial Distribution"),
	NTRANS("Axial Distribution"),
	NTRANS("Binomial Distribution"),
	NTRANS("Point Em/Replacement"),
	NTRANS("Local Chemistry"),
	NTRANS("Local Chem. Filter"),
	NTRANS("Axial DF Sweep"),
	};

const char *STOP_MODES[] = {
	NTRANS("Neighbour Count"),
	NTRANS("Radius")
};

//User visible names for the replace sub-algorithms
const char *REPLACE_ALGORITHMS[] = { "Subtract",
					"Intersect",
					"Union",
					};
					

//Switch to determine if algorithms need range propagation or not
const bool WANT_RANGE_PROPAGATION[] = { false, 
					true,
					false,
					false,
					false,
					true,
					false,
					true,
					false,
					};


//Default distance to use when performing axial distance computations
const float DEFAULT_AXIAL_DISTANCE = 1.0f;

const float DISTANCE_EPSILON=sqrt(std::numeric_limits<float>::epsilon());


//Helper function for computing a weighted mean
float weightedMean(const vector<float> &x, const vector<float> &y,bool zeroOutSingularity=true)
{
	ASSERT(x.size() == y.size());

	float num=0,denom=0;
	for(size_t ui=0;ui<y.size();ui++)
	{
		num+=y[ui]*x[ui];
		denom+=y[ui];
	}

	if(zeroOutSingularity)
	{
		if(denom <std::numeric_limits<float>::epsilon())
			return 0;
	}

	ASSERT(denom);
	return num/denom;
}


//Scan input datastreams to build two point vectors,
// one of those with points specified as "target" 
// which is a copy of the input points

SpatialAnalysisFilter::SpatialAnalysisFilter()
{
	COMPILE_ASSERT(THREEDEP_ARRAYSIZE(STOP_MODES) == STOP_MODE_ENUM_END);
	COMPILE_ASSERT(THREEDEP_ARRAYSIZE(SPATIAL_ALGORITHMS) == ALGORITHM_ENUM_END);
	COMPILE_ASSERT(THREEDEP_ARRAYSIZE(WANT_RANGE_PROPAGATION) == ALGORITHM_ENUM_END);
	COMPILE_ASSERT(THREEDEP_ARRAYSIZE(REPLACE_ALGORITHMS) == REPLACE_MODE_ENUM_END);
	
	
	algorithm=ALGORITHM_DENSITY;
	nnMax=1;
	distMax=1;
	stopMode=STOP_MODE_NEIGHBOUR;

	haveRangeParent=false;
	
	//Default colour is red
	rgba=ColourRGBAf(1.0f,0,0);

	//RDF params
	numBins=100;
	excludeSurface=false;
	reductionDistance=distMax;
	normaliseNNHist=true;
	normaliseRDF=false;

	//Density filtering params
	densityCutoff=1.0f;
	keepDensityUpper=true;
	wantRandomNNHist=true;

	//Binomial parameters
	//--
	numIonsSegment = 200;
	showBinomialFrequencies=true;
	showNormalisedBinomialFrequencies=true;
	showTheoreticFrequencies=true;
	extrusionDirection=0;
	maxBlockAspect=2;
	showGridOverlay=true;
	//--

	//replace tolerance
	replaceTolerance=sqrtf(std::numeric_limits<float>::epsilon());
	replaceMode=REPLACE_MODE_SUBTRACT;
	replaceMass=true;


	//Angluar step for DF sweep
	dAngularStep=2.0f;
	wantDFLogScaled=true;
	dfColourMap=COLOURMAP_VIRIDIS;
	angleStartStop[0][0]=0;
	angleStartStop[0][1]=90;
	angleStartStop[1][0]=0;
	angleStartStop[1][1]=180;

	cacheOK=false;
	cache=true; //By default, we should cache, but decision is made higher up

}

vector<bool> SpatialAnalysisFilter::expandEnabledToParentRange(const vector<bool> &vb, const RangeFile *rngF) const
{

	ASSERT(vb.size() <= rngF->getNumIons());
	ASSERT(vb.size() <= ionNames.size());

	vector<bool> result;
	result.resize(rngF->getNumIons(),false);
	for(unsigned int ui=0;ui<vb.size(); ui++)
	{
		unsigned int ionID;
		ionID=rngF->getIonID(ionNames[ui]);
		ASSERT(ionID != (unsigned int) -1);
		result[ionID] = vb[ui];
	}

	return result;
}
Filter *SpatialAnalysisFilter::cloneUncached() const
{
	SpatialAnalysisFilter *p=new SpatialAnalysisFilter;

	p->rgba=rgba;
	
	p->algorithm=algorithm;
	p->stopMode=stopMode;
	p->nnMax=nnMax;
	p->distMax=distMax;

	p->numBins=numBins;
	p->excludeSurface=excludeSurface;
	p->reductionDistance=reductionDistance;
	p->normaliseNNHist = normaliseNNHist;
	p->wantRandomNNHist=wantRandomNNHist;
	
	p->keepDensityUpper=keepDensityUpper;
	p->densityCutoff=densityCutoff;
	
	p->numIonsSegment=numIonsSegment;
	p->maxBlockAspect=maxBlockAspect;
	p->binWidth=binWidth;
	p->extrusionDirection=extrusionDirection;
	p->showBinomialFrequencies=showBinomialFrequencies;
	p->showNormalisedBinomialFrequencies=showNormalisedBinomialFrequencies;
	p->showTheoreticFrequencies=showTheoreticFrequencies;
	p->showGridOverlay=showGridOverlay;

	p->replaceFile=replaceFile;
	p->replaceMode=replaceMode;
	p->replaceTolerance=replaceTolerance;
	p->replaceMass=replaceMass;

	p->dAngularStep = dAngularStep;
	for(unsigned int ui=0; ui<2; ui++)
		for(unsigned int uj=0; uj<2; uj++)
		p->angleStartStop[ui][uj]=angleStartStop[ui][uj];

	//We are copying whether to cache or not,
	//not the cache itself
	p->cache=cache;
	p->cacheOK=false;
	p->userString=userString;

	p->vectorParams=vectorParams;
	p->scalarParams=scalarParams;

	p->ionSourceEnabled=ionSourceEnabled;
	p->ionTargetEnabled=ionTargetEnabled;
	p->ionNumeratorEnabled=ionNumeratorEnabled;
	p->ionDenominatorEnabled=ionDenominatorEnabled;
	
	return p;
}

size_t SpatialAnalysisFilter::numBytesForCache(size_t nObjects) const
{
	return nObjects*IONDATA_SIZE;
}

unsigned int SpatialAnalysisFilter::getNumSteps() const
{
    switch(algorithm)
    {
	    case ALGORITHM_DENSITY:
		return 3;
	    case ALGORITHM_DENSITY_FILTER:
		return 3;
	    case ALGORITHM_RDF:
	    {
		if(excludeSurface)
		    return 4;
		else
		    return 3;
	    }
	    case ALGORITHM_AXIAL_DF:
		return 3;
	    case ALGORITHM_BINOMIAL:
		return 2;
	    case ALGORITHM_REPLACE:
		return 4;
	    case ALGORITHM_LOCAL_CHEMISTRY:
	    {
		if(stopMode == STOP_MODE_RADIUS)
		    return 4;
		if(stopMode == STOP_MODE_NEIGHBOUR)
		    return 3;
		ASSERT(false);
	    }
	    case ALGORITHM_LOCAL_CHEMISTRY_FILTER:
	    {
		if(stopMode == STOP_MODE_RADIUS)
		    return 4;
		if(stopMode == STOP_MODE_NEIGHBOUR)
		    return 3;
		ASSERT(false);
	    }
	    case ALGORITHM_AXIAL_DF_SWEEP:
		return 3;
		default:
			ASSERT(false);
    }
}
void SpatialAnalysisFilter::initFilter(const std::vector<const FilterStreamData *> &dataIn,
				std::vector<const FilterStreamData *> &dataOut)
{
	//Check for range file parent
	for(unsigned int ui=0;ui<dataIn.size();ui++)
	{
		if(dataIn[ui]->getStreamType() == STREAM_TYPE_RANGE)
		{
			const RangeStreamData *r;
			r = (const RangeStreamData *)dataIn[ui];

			if(WANT_RANGE_PROPAGATION[algorithm])
				dataOut.push_back(dataIn[ui]);

			bool different=false;
			if(!haveRangeParent)
			{
				//well, things have changed, we didn't have a 
				//range parent before.
				different=true;
			}
			else
			{
				//OK, last time we had a range parent. Check to see 
				//if the ion names are the same. If they are, keep the 
				//current bools, iff the ion names are all the same
				unsigned int numEnabled=std::count(r->enabledIons.begin(),
							r->enabledIons.end(),1);
				if(ionNames.size() == numEnabled)
				{
					unsigned int pos=0;
					for(unsigned int uj=0;uj<r->rangeFile->getNumIons();uj++)
					{
						//Only look at parent-enabled ranges
						if(r->enabledIons[uj])
						{
							if(r->rangeFile->getName(uj) != ionNames[pos])
							{
								different=true;
								break;
							}
							pos++;
			
						}
					}
				}
				else
					different=true;
			}
			haveRangeParent=true;

			if(different)
			{
				//OK, its different. we will have to re-assign,
				//but only allow the ranges enabled in the parent filter
				ionNames.clear();
				ionNames.reserve(r->rangeFile->getNumRanges());
				for(unsigned int uj=0;uj<r->rangeFile->getNumIons();uj++)
				{

					if(r->enabledIons[uj])
						ionNames.push_back(r->rangeFile->getName(uj));
				}

				ionSourceEnabled.resize(ionNames.size(),true);
				ionTargetEnabled.resize(ionNames.size(),true);
				
				ionNumeratorEnabled.resize(ionNames.size(),true);
				ionDenominatorEnabled.resize(ionNames.size(),true);
			}

			return;
		}
	}
	haveRangeParent=false;
}



unsigned int SpatialAnalysisFilter::refresh(const std::vector<const FilterStreamData *> &dataIn,
	std::vector<const FilterStreamData *> &getOut, ProgressData &progress)
{
	//use the cached copy if we have it.
	if(cacheOK)
	{
		size_t mask=STREAM_TYPE_IONS;
		if(!WANT_RANGE_PROPAGATION[algorithm])
			mask|=STREAM_TYPE_RANGE;


		//Propagate input streams as desired, propagate everything but the 
		// ion inputs
		propagateStreams(dataIn,getOut,mask,true);
	
		//Propagate cached objects
		propagateCache(getOut);
		return 0;
	}

	//Set K3D tree abort pointer and progress
	K3DTree::setAbortFlag(Filter::wantAbort);
	K3DTree::setProgressPtr(&progress.filterProgress);

	K3DTreeMk2::setAbortFlag(Filter::wantAbort);
	K3DTreeMk2::setProgressPtr(&progress.filterProgress);

	//Find out how much total size we need in points vector
	size_t totalDataSize=numElements(dataIn,STREAM_TYPE_IONS);

	//Nothing to do, but propagate inputs
	if(!totalDataSize)
	{
		propagateStreams(dataIn,getOut,getRefreshBlockMask());
		return 0;
	}

	const RangeFile *rngF=0;
	if(haveRangeParent)
	{
		//Check we actually have something to do
		if(!std::count(ionSourceEnabled.begin(),
					ionSourceEnabled.end(),true))
			return 0;
		if(!std::count(ionTargetEnabled.begin(),
					ionTargetEnabled.end(),true))
			return 0;

		rngF=getRangeFile(dataIn);
	}


	progress.maxStep=getNumSteps();	
	size_t result;
	
	//Run the algorithm
	switch(algorithm)
	{
		case ALGORITHM_DENSITY:
			result=algorithmDensity(progress,totalDataSize,
					dataIn,getOut);
			break;
		case ALGORITHM_RDF:
			result=algorithmRDF(progress,totalDataSize,
					dataIn,getOut,rngF);
			break;
		case ALGORITHM_DENSITY_FILTER:
			result=algorithmDensityFilter(progress,totalDataSize,
					dataIn,getOut);
			break;
		case ALGORITHM_AXIAL_DF:
			result=algorithmAxialDf(progress,totalDataSize,
					dataIn,getOut,rngF);
			break;
		case ALGORITHM_BINOMIAL:
		{
			if(!rngF)
				return ERR_NO_RANGE;
			
			result=algorithmBinomial(progress,totalDataSize,
					dataIn,getOut,rngF);
			break;
		}
		case ALGORITHM_REPLACE:
			result=algorithmReplace(progress,totalDataSize,
						dataIn,getOut);
			break;
		case ALGORITHM_LOCAL_CHEMISTRY:
			if(!rngF)
				return ERR_NO_RANGE;
			result=algorithmLocalConcentration(progress,totalDataSize,
						dataIn,getOut,rngF);
			break;
		case ALGORITHM_LOCAL_CHEMISTRY_FILTER:
			if(!rngF)
				return ERR_NO_RANGE;
			result=algorithmLocalConcentrationFilter(progress,totalDataSize,
						dataIn,getOut,rngF);
			break;
		case ALGORITHM_AXIAL_DF_SWEEP:
			result=algorithmAxialDfSweep(progress,totalDataSize,dataIn,getOut);
			break;
		default:
			ASSERT(false);
	}
	
	return result;
}

size_t SpatialAnalysisFilter::algorithmReplace(ProgressData &progress, size_t totalDataSize, 
			const vector<const FilterStreamData *>  &dataIn, 
			vector<const FilterStreamData * > &getOut)
{
	progress.step=1;
	progress.stepName=TRANS("Collate");
	progress.filterProgress=0;

	//Merge the ions form the incoming streams
	vector<IonHit> inIons;
	Filter::collateIons(dataIn,inIons,progress,totalDataSize);
	
	progress.step=2;
	progress.stepName=TRANS("Load");
	progress.filterProgress=0;

	vector<IonHit> fileIons;
	const unsigned int loadPositions[] = {
						0,1,2,3};

	//Load the other dataset
	unsigned int errCode=GenericLoadFloatFile(4,4,loadPositions,
			fileIons,replaceFile.c_str(),progress.filterProgress,*Filter::wantAbort);

	if(errCode)
		return ERR_FILE_READ_FAIL;


	vector<IonHit> outIons;
	if(inIons.empty() || fileIons.empty())
	{
		//Performance increase if we have an empty item.
		// - in this case we can swap sets around
		switch(replaceMode)
		{
			case REPLACE_MODE_UNION:
			{
				//If the local data is empty, then the union is just the "b" data (swap).
				// if nonempty, then it is simply the "a" data 
				if(inIons.empty())
					outIons.swap(fileIons);
				else
					outIons.swap(inIons);
				break;
			}
			case REPLACE_MODE_SUBTRACT:
			{
				//if either localdata OR bdata is empty, then we don't need to do anything.
				// either way, input stays as it is
				outIons.swap(inIons);
				break;
			}
			case REPLACE_MODE_INTERSECT:
			{
				//intersection with empty set is empty set.
				// might as well clear the ions incoming
				inIons.clear();
				break;
			}
			default:
				ASSERT(false);

		}
	}
	else
	{

		progress.step=3;
		progress.stepName=TRANS("Build");
		progress.filterProgress=0;

		//TODO: Possible speed increase by finding the smaller of
		// the two inputs, and using that to build the tree

		//Build the search tree we will use to perform replacement
		K3DTreeMk2 tree;
		// reset the tree points, without deleting the fileIOns
		tree.resetPts(fileIons,false);
		if(!tree.build())
			return ERR_ABORT_FAIL;
		BoundCube b;
		tree.getBoundCube(b);

		//map the offset of the nearest to
		//the tree ID 
		vector<size_t > nearestVec;
		nearestVec.resize(inIons.size());

		//TODO: pair vector might be faster
		// as we can use it in sequence, and can use openmp
		map<size_t,size_t> matchedMap;

		//Find the nearest point for all points in the dataset
		// maps the ith ion in "inions" to the tree value
		#pragma omp parallel for 
		for(size_t ui=0;ui<inIons.size();ui++)
		{
			nearestVec[ui]=tree.findNearestUntagged(inIons[ui].getPos(),b,false);
		}

		float sqrReplaceTol=replaceTolerance*replaceTolerance;

		//Filter this to only points that had an NN within range
		#pragma omp parallel for 
		for(size_t ui=0;ui<inIons.size();ui++)
		{
			if(nearestVec[ui]!=(size_t)-1 && inIons[ui].getPos().sqrDist(*tree.getPt(nearestVec[ui])) <=sqrReplaceTol)
			{
				#pragma omp critical
				matchedMap[ui]=tree.getOrigIndex(nearestVec[ui]);
			}
		}

		nearestVec.clear();


		progress.step=4;
		progress.stepName=TRANS("Compute");
		progress.filterProgress=0;


		//now we have a map that matches as so:
		// map ( "inIon" ID -> "fileIon" ID)
		// inIon should be our "A" in "A operator B"
		switch(replaceMode)
		{
			case REPLACE_MODE_SUBTRACT:
			{
				//If no matches, A-0 = A. Just return input
				if(matchedMap.empty())
				{
					outIons.swap(inIons);
					break;
				}
				//In subtraction mode, we should have
				// at least this many ions
				if(inIons.size() > matchedMap.size())
					outIons.reserve(inIons.size()-matchedMap.size());
				
				//
				#pragma omp parallel for
				for(unsigned int ui=0;ui<inIons.size();ui++)
				{
					map<size_t,size_t>::iterator it;
					it=matchedMap.find(ui);
					if(it != matchedMap.end())
						continue;

					#pragma omp critical
					outIons.push_back(inIons[ui]);
				}
				break;
			}
			case REPLACE_MODE_INTERSECT:
			{
				//Finish if no matches
				if(matchedMap.empty())
					break;
				
				outIons.reserve(matchedMap.size());

				if(replaceMass)
				{
					for(map<size_t,size_t>::const_iterator it=matchedMap.begin();it!=matchedMap.end();++it)
					{
						outIons.push_back(fileIons[it->second]);
						ASSERT(fileIons[it->second].getPosRef().sqrDist(inIons[it->first].getPosRef()) < sqrReplaceTol);
					}
				}
				else
				{
					for(map<size_t,size_t>::const_iterator it=matchedMap.begin();it!=matchedMap.end();++it)
					{
						outIons.push_back(inIons[it->first]);
					}
				}
				break;
			}
			case REPLACE_MODE_UNION:
			{
				outIons.swap(fileIons);
				outIons.reserve(outIons.size() + fileIons.size() - matchedMap.size());
				map<size_t,size_t>::const_iterator it=matchedMap.begin();
				

				for(unsigned int ui=0;ui<inIons.size();ui++)
				{
					if(it !=matchedMap.end() && (it->first == ui) )
					{
						++it;
						continue;
					}


					outIons.push_back(inIons[ui]);
				}


				break;
			}
			default:
				ASSERT(false);
		}
	}

	//Only output ions if any were found
	if(outIons.size())
	{
		IonStreamData *outData = new IonStreamData(this);

		outData->g = outData->b = outData->r = 0.5;
		outData->data.swap(outIons);
		cacheAsNeeded(outData);


		getOut.push_back(outData);
	}

	return 0;
}

void SpatialAnalysisFilter::getProperties(FilterPropGroup &propertyList) const
{
	size_t curGroup=0;

	string tmpStr;
	vector<pair<unsigned int,string> > choices;

	for(unsigned int ui=0;ui<ALGORITHM_ENUM_END;ui++)
	{
		tmpStr=TRANS(SPATIAL_ALGORITHMS[ui]);
		choices.emplace_back(ui,tmpStr);
	}	
	FilterProperty pAlgorithm(KEY_ALGORITHM, TRANS("Algorithm"),
			choices,algorithm,TRANS("Spatial analysis algorithm to use"));
	propertyList.addProperty(pAlgorithm,curGroup);
	choices.clear();

	propertyList.setGroupTitle(curGroup,TRANS("Algorithm"));
	curGroup++;
	
	//Get the options for the current algorithm
	//---

	//common options between several algorithms
	if(algorithm ==  ALGORITHM_RDF
		||  algorithm == ALGORITHM_DENSITY 
		|| algorithm == ALGORITHM_DENSITY_FILTER 
		|| algorithm == ALGORITHM_AXIAL_DF
		|| algorithm == ALGORITHM_LOCAL_CHEMISTRY
		|| algorithm == ALGORITHM_LOCAL_CHEMISTRY_FILTER)
		
	{
		tmpStr=TRANS(STOP_MODES[STOP_MODE_NEIGHBOUR]);

		choices.emplace_back((unsigned int)STOP_MODE_NEIGHBOUR,tmpStr);
		tmpStr=TRANS(STOP_MODES[STOP_MODE_RADIUS]);
		choices.emplace_back((unsigned int)STOP_MODE_RADIUS,tmpStr);
		
		FilterProperty pMode(KEY_STOPMODE, TRANS("Stop Mode"),
				choices,stopMode,TRANS("Method to use to terminate algorithm when examining each point"));
		propertyList.addProperty(pMode,curGroup);
		choices.clear();

		if(stopMode == STOP_MODE_NEIGHBOUR)
		{
			FilterProperty pNNMax(KEY_NNMAX, TRANS("NN Max"),
				nnMax,TRANS("Maximum number of neighbours to examine"));
			propertyList.addProperty(pNNMax,curGroup);
			
			if(algorithm == ALGORITHM_RDF)
			{

				FilterProperty pNormalise(KEY_NNMAX_NORMALISE,TRANS("Normalise bins"),
					normaliseNNHist,TRANS("Normalise counts by binwidth. Needed when comparing NN histograms against one another"));
				propertyList.addProperty(pNormalise,curGroup);
				
				FilterProperty pShowRandom(KEY_NNMAX_SHOWRANDOM,TRANS("Show Random"),
					wantRandomNNHist,TRANS("Show a fitted (density matched) theoretical distribution"));
				propertyList.addProperty(pShowRandom,curGroup);

			}
		}
		else
		{
			FilterProperty pNNMax(KEY_DISTMAX, TRANS("Dist Max"),
				distMax,TRANS("Maximum distance from each point for search"));
			propertyList.addProperty(pNNMax,curGroup);
		}

		propertyList.setGroupTitle(curGroup,TRANS("Stop Mode"));
	}
	
	//Extra options for specific algorithms 
	switch(algorithm)
	{
		case ALGORITHM_RDF:
		{
			FilterProperty pNumBins(KEY_NUMBINS, TRANS("Num Bins"),
				numBins,TRANS("Number of bins for output 1D RDF plot"));
			propertyList.addProperty(pNumBins,curGroup);

			FilterProperty pExcludeSurface(KEY_REMOVAL, TRANS("Surface Remove"),
				excludeSurface,TRANS("Exclude surface as part of source to minimise bias in RDF (at cost of increased noise)"));
			propertyList.addProperty(pExcludeSurface,curGroup);

			if(excludeSurface)
			{

				FilterProperty pReductionDist(KEY_REDUCTIONDIST, TRANS("Remove Dist"),
					reductionDistance,TRANS("Minimum distance to remove from surface"));
				propertyList.addProperty(pReductionDist,curGroup);
			}

			if(stopMode == STOP_MODE_RADIUS)
			{
				FilterProperty pNormalise(KEY_NORMALISERDF, TRANS("Normalise"),
					normaliseRDF,TRANS("Perform normalisation of the RDF, rather than raw counts"));
				propertyList.addProperty(pNormalise,curGroup);
			}

			FilterProperty pColour(KEY_COLOUR, TRANS("Plot colour"),
					rgba,TRANS("Colour of output plot"));
			propertyList.addProperty(pColour,curGroup);
				

			propertyList.setGroupTitle(curGroup,TRANS("Alg. Params."));
			if(haveRangeParent)
			{
				ASSERT(ionSourceEnabled.size() == ionNames.size());
				ASSERT(ionNames.size() == ionTargetEnabled.size());
				curGroup++;

				bool allSrcEnable;
				allSrcEnable= ((size_t)std::count(ionSourceEnabled.begin(),
					ionSourceEnabled.end(),true) == ionSourceEnabled.size());

				FilterProperty pSource(KEY_ENABLE_SOURCE_ALL, TRANS("Source"),
						allSrcEnable,TRANS("Ions to use for initiating RDF search"));
				propertyList.addProperty(pSource,curGroup);
					

				//Loop over the possible incoming ranges,
				//once to set sources, once to set targets
				for(unsigned int ui=0;ui<ionSourceEnabled.size();ui++)
				{
					FilterProperty p;
					string sTmp;

					sTmp=boolStrEnc(ionSourceEnabled[ui]);
					p.name=ionNames[ui];
					p.data=sTmp;
					p.type=PROPERTY_TYPE_BOOL;
					p.helpText=TRANS("Enable/disable ion as source");
					p.key=muxKey(KEYTYPE_ENABLE_SOURCE,ui);
					propertyList.addProperty(p,curGroup);
				}
				
				propertyList.setGroupTitle(curGroup,TRANS("Source Ion"));

				curGroup++;
				
				bool allTargetEnable = ((size_t)std::count(ionTargetEnabled.begin(),
					ionTargetEnabled.end(),true) == ionTargetEnabled.size());
				
				FilterProperty pTarget(KEY_ENABLE_TARGET_ALL, TRANS("Target"),
						allTargetEnable,TRANS("Enable/disable all ions as target"));
				propertyList.addProperty(pTarget,curGroup);
				
				//Loop over the possible incoming ranges,
				//once to set sources, once to set targets
				for(unsigned int ui=0;ui<ionTargetEnabled.size();ui++)
				{
					string sTmp;
					FilterProperty p;

					sTmp=boolStrEnc(ionTargetEnabled[ui]);
					p.name=ionNames[ui];
					p.data=sTmp;
					p.type=PROPERTY_TYPE_BOOL;
					p.helpText=TRANS("Enable/disable this ion as target");
					p.key=muxKey(KEYTYPE_ENABLE_TARGET,ui);
					propertyList.addProperty(p,curGroup);
				}
				propertyList.setGroupTitle(curGroup,TRANS("Target Ion"));

			}
	
			break;
		}	
		case ALGORITHM_DENSITY_FILTER:
		{
			FilterProperty pCutoff(KEY_CUTOFF, TRANS("Cutoff"),
					densityCutoff,TRANS("Remove points with local density above/below this value"));
			propertyList.addProperty(pCutoff,curGroup);
			
			FilterProperty pKeepUpperDensity(KEY_RETAIN_UPPER, TRANS("Retain Upper"),
					keepDensityUpper,TRANS("Retain either points with density above (enabled) or below cutoff"));
			propertyList.addProperty(pKeepUpperDensity,curGroup);
			
			propertyList.setGroupTitle(curGroup,TRANS("Alg. Params."));
			break;
		}
		case ALGORITHM_DENSITY:
		{
			propertyList.setGroupTitle(curGroup,TRANS("Alg. Params."));
			break;
		}
		case ALGORITHM_AXIAL_DF:
		{
			FilterProperty pNumBins(KEY_NUMBINS, TRANS("Num Bins"),
					numBins,TRANS("Number of bins for output 1D RDF plot"));
			propertyList.addProperty(pNumBins,curGroup);

			FilterProperty pColour(KEY_COLOUR, TRANS("Plot Colour"),
					rgba,TRANS("Colour of output plot"));
			propertyList.addProperty(pColour,curGroup);
			
			FilterProperty pAxis(KEY_NORMAL, TRANS("Axis"),
					vectorParams[0],TRANS("Vector along which to calculate distribution function"));
			propertyList.addProperty(pAxis,curGroup);
	
			propertyList.setGroupTitle(curGroup,TRANS("Alg. Params."));
			break;
		}
		case ALGORITHM_BINOMIAL:
		{
			FilterProperty pNumIonsBlock(KEY_NUMIONS, TRANS("Block size"),
					numIonsSegment,TRANS("Number of ions to use per block"));
			propertyList.addProperty(pNumIonsBlock,curGroup);
			
			FilterProperty pMaxAspect(KEY_BINOMIAL_MAX_ASPECT, TRANS("Max Block Aspect"),
					maxBlockAspect,TRANS("Maximum allowable block aspect ratio. Blocks above this aspect are discarded. Setting too high decreases correlation strength. Too low causes loss of statistical power."));
			propertyList.addProperty(pMaxAspect,curGroup);
			
			//--
			//TODO: Explain why these magic numbers are not x=0,y=1...??
			vector<pair<unsigned int, string> > choices;
			choices.emplace_back(1,"x");	
			choices.emplace_back(2,"y");	
			choices.emplace_back(0,"z");	
		
			FilterProperty pExtrusion(KEY_BINOMIAL_EXTRUDE_DIR, TRANS("Extrusion Direction"),
				choices,extrusionDirection,TRANS("Direction in which blocks are extended during construction."));
			propertyList.addProperty(pExtrusion,curGroup);
			//--
	
			propertyList.setGroupTitle(curGroup,TRANS("Alg. Params."));
			
			curGroup++;
			
			FilterProperty pShowCounts(KEY_SHOW_BINOM_FREQ, TRANS("Plot Counts"),
					showBinomialFrequencies,TRANS("Show the counts in the binomial histogram"));
			propertyList.addProperty(pShowCounts,curGroup);

			if(showBinomialFrequencies)
			{
				FilterProperty pShowNormalisedCounts(KEY_SHOW_BINOM_NORM_FREQ, TRANS("Normalise"),
						showNormalisedBinomialFrequencies,TRANS("Normalise the counts in the binomial histogram to a probability density function"));
				propertyList.addProperty(pShowNormalisedCounts,curGroup);

				FilterProperty pExpectedFreq(KEY_SHOW_BINOM_THEOR_FREQ, 
					TRANS("Expected Freq"), showNormalisedBinomialFrequencies,
					TRANS("Show the theoretically expected probability density function for a random dataset with the same mean"));
				propertyList.addProperty(pExpectedFreq,curGroup);



			}
			
			FilterProperty pShowGrid(KEY_SHOW_BINOM_3D_GRID, 
				TRANS("Display Grid"), showGridOverlay,
				TRANS("Show the extruded grid in the 3D view. This may be slow"));
			propertyList.addProperty(pShowGrid,curGroup);

			propertyList.setGroupTitle(curGroup,TRANS("View Options"));


			if(haveRangeParent)
			{
				ASSERT(ionSourceEnabled.size() == ionNames.size());
				ASSERT(ionNames.size() == ionTargetEnabled.size());
				curGroup++;

				bool allSrcEnable;
				allSrcEnable= ((size_t)std::count(ionSourceEnabled.begin(),
					ionSourceEnabled.end(),true) == ionSourceEnabled.size());

				FilterProperty pSource(KEY_ENABLE_SOURCE_ALL, TRANS("Ions"),
						allSrcEnable,TRANS("Ions to use when counting"));
				propertyList.addProperty(pSource,curGroup);

				//Loop over the possible incoming ranges,
				for(unsigned int ui=0;ui<ionSourceEnabled.size();ui++)
				{
					FilterProperty p;
					string sTmp;

					sTmp=boolStrEnc(ionSourceEnabled[ui]);
					p.name=ionNames[ui];
					p.data=sTmp;
					p.type=PROPERTY_TYPE_BOOL;
					p.helpText=TRANS("Enable/disable ion");
					p.key=muxKey(KEYTYPE_ENABLE_SOURCE,ui);
					propertyList.addProperty(p,curGroup);
				}

				propertyList.setGroupTitle(curGroup,TRANS("Enabled Ions"));

				curGroup++;
			}
			break;	
		}
		case ALGORITHM_REPLACE:
		{
			FilterProperty pReplaceFile(KEY_REPLACE_FILE, PROPERTY_TYPE_FILE,
				TRANS("Data File"), replaceFile,TRANS("Pos file of points to subtract/replace/etc"));
			pReplaceFile.dataSecondary="Pos File (*.pos)|*.pos|All Files|*";
			propertyList.addProperty(pReplaceFile,curGroup);
		
			FilterProperty pMatchTol(KEY_REPLACE_TOLERANCE, TRANS("Match Tol."), 
					replaceTolerance,TRANS("Tolerance to allow for matching"));
			propertyList.addProperty(pMatchTol,curGroup);
		
			vector<pair<unsigned int,string> > choices;

			for(unsigned int ui=0;ui<REPLACE_MODE_ENUM_END;ui++)
			{
				tmpStr=TRANS(REPLACE_ALGORITHMS[ui]);
				choices.emplace_back(ui,tmpStr);
			}	
		
			FilterProperty pReplaceMode(KEY_REPLACE_ALGORITHM, TRANS("Mode"), 
					choices,replaceMode,TRANS("Replacment condition"));
			propertyList.addProperty(pReplaceMode,curGroup);

			if(replaceMode != REPLACE_MODE_SUBTRACT)
			{
				FilterProperty pReplaceMode(KEY_REPLACE_VALUE, TRANS("Replace value"), 
						replaceMass,TRANS("Use value data from file when replacing ions"));
				propertyList.addProperty(pReplaceMode,curGroup);
			}

			propertyList.setGroupTitle(curGroup,TRANS("Replacement"));
			break;
		}
		case ALGORITHM_LOCAL_CHEMISTRY:
		case ALGORITHM_LOCAL_CHEMISTRY_FILTER:
		{
			if (algorithm == ALGORITHM_LOCAL_CHEMISTRY_FILTER)
			{
				curGroup++;
				
				FilterProperty pCutoff(KEY_CUTOFF, TRANS("Cutoff"),
						densityCutoff,TRANS("Remove points with local concentration above/below this value"));
				propertyList.addProperty(pCutoff,curGroup);
				
				FilterProperty pKeepUpperDensity(KEY_RETAIN_UPPER, TRANS("Retain Upper"),
						keepDensityUpper,TRANS("Retain either points with concentration above (enabled) or below cutoff"));
				propertyList.addProperty(pKeepUpperDensity,curGroup);
				
				propertyList.setGroupTitle(curGroup,TRANS("Alg. Params."));
			}
			
			if(haveRangeParent)
			{
				ASSERT(ionSourceEnabled.size() == ionNames.size());
				ASSERT(ionNames.size() == ionTargetEnabled.size());
				curGroup++;

				
				bool bEnableAll;

				bEnableAll = ((size_t)std::count(ionSourceEnabled.begin(),
					ionSourceEnabled.end(),true) == ionSourceEnabled.size());

				FilterProperty pSource(KEY_ENABLE_SOURCE_ALL, TRANS("Source"), 
						bEnableAll,TRANS("Enable/disable all ions as source"));
				propertyList.addProperty(pSource,curGroup);

				FilterProperty p;
				//Loop over the possible incoming ranges,
				//once to set sources, once to set targets
				for(unsigned int ui=0;ui<ionSourceEnabled.size();ui++)
				{
					string sTmp;
					sTmp=boolStrEnc(ionSourceEnabled[ui]);
					p.name=ionNames[ui];
					p.data=sTmp;
					p.type=PROPERTY_TYPE_BOOL;
					p.helpText=TRANS("Enable/disable ion as source");
					p.key=muxKey(KEYTYPE_ENABLE_SOURCE,ui);
					propertyList.addProperty(p,curGroup);
				}
				
				propertyList.setGroupTitle(curGroup,TRANS("Source Ion"));
				curGroup++;


				bool enableNumeratorAll= ((size_t)std::count(ionNumeratorEnabled.begin(),
					ionNumeratorEnabled.end(),true) == ionNumeratorEnabled.size());
				FilterProperty pNumerator(KEY_ENABLE_NUMERATOR_ALL, TRANS("Numerator"),
						enableNumeratorAll,TRANS("Enable/Disable all ions to use as Numerator for conc. calculation"));
				propertyList.addProperty(pNumerator,curGroup);

					
				//Loop over the possible incoming ranges,
				//once to set sources, once to set targets
				for(unsigned int ui=0;ui<ionNumeratorEnabled.size();ui++)
				{
					string sTmp;
					sTmp=boolStrEnc(ionNumeratorEnabled[ui]);
					p.name=ionNames[ui];
					p.data=sTmp;
					p.type=PROPERTY_TYPE_BOOL;
					p.helpText=TRANS("Enable/disable ion as source");
					p.key=muxKey(KEYTYPE_ENABLE_NUMERATOR,ui);
					propertyList.addProperty(p,curGroup);
				}
				
				propertyList.setGroupTitle(curGroup,TRANS("Numerator"));
				curGroup++;

				
				bool enableDenominatorAll =((size_t)std::count(ionDenominatorEnabled.begin(),
					ionDenominatorEnabled.end(),true) == ionDenominatorEnabled.size());
				FilterProperty pDenominator(KEY_ENABLE_DENOMINATOR_ALL, TRANS("Denominator"),
						enableDenominatorAll,TRANS("Enable/Disable all ions to use as Denominator for conc. calculation"));
				propertyList.addProperty(pDenominator,curGroup);
				
				//Loop over the possible incoming ranges,
				//once to set sources, once to set targets
				for(unsigned int ui=0;ui<ionDenominatorEnabled.size();ui++)
				{
					string sTmp;
					sTmp=boolStrEnc(ionDenominatorEnabled[ui]);
					p.name=ionNames[ui];
					p.data=sTmp;
					p.type=PROPERTY_TYPE_BOOL;
					p.helpText=TRANS("Enable/disable this ion as target");
					p.key=muxKey(KEYTYPE_ENABLE_DENOMINATOR,ui);
					propertyList.addProperty(p,curGroup);
				}
				propertyList.setGroupTitle(curGroup,TRANS("Denominator")); 
			}
	
			break;
		}	
		case ALGORITHM_AXIAL_DF_SWEEP:
		{
			FilterProperty pAngularStep(KEY_ANGULARSTEP, TRANS("Angular step (deg)"),
					dAngularStep,TRANS("Step in degrees between calculations for angular histogram"));
			propertyList.addProperty(pAngularStep,curGroup);
			
			FilterProperty pDistMax(KEY_DISTMAX, TRANS("Dist. Max"),
					distMax,TRANS("Maximum distance from each point for search"));
			propertyList.addProperty(pDistMax,curGroup);
			
			propertyList.setGroupTitle(curGroup,TRANS("Alg. Params."));

			curGroup++;
			FilterProperty pDFLog(KEY_SHOW_DF_LOGSCALE, TRANS("Log scale"),
					wantDFLogScaled,TRANS("Show 2D plot in logarithmic scale"));
			propertyList.addProperty(pDFLog,curGroup);
			
			for(unsigned int ui=0;ui<COLOURMAP_ENUM_END; ui++)
				choices.emplace_back(ui,getColourMapName(ui));
			FilterProperty pColourMap(KEY_DF_COLOURMAP, TRANS("Colour Map"),choices,
					dfColourMap,TRANS("Colour scheme in 2D plot"));
			propertyList.addProperty(pColourMap,curGroup);
			propertyList.setGroupTitle(curGroup,TRANS("Display"));
			break;
		}
		default:
			ASSERT(false);
	}
	
	//---
}

bool SpatialAnalysisFilter::setProperty(  unsigned int key,
					const std::string &value, bool &needUpdate)
{

	needUpdate=false;
	switch(key)
	{
		case KEY_ALGORITHM:
		{
			size_t ltmp=ALGORITHM_ENUM_END;
			for(unsigned int ui=0;ui<ALGORITHM_ENUM_END;ui++)
			{
				if(value == TRANS(SPATIAL_ALGORITHMS[ui]))
				{
					ltmp=ui;
					break;
				}
			}


			
			if(ltmp>=ALGORITHM_ENUM_END)
				return false;
		
			if((ltmp == ALGORITHM_LOCAL_CHEMISTRY || ltmp == ALGORITHM_LOCAL_CHEMISTRY_FILTER) &&
				nnMax < 2)
			{
				nnMax=2;
			}
	
			algorithm=ltmp;
			resetParamsAsNeeded();
			needUpdate=true;
			clearCache();

			break;
		}	
		case KEY_STOPMODE:
		{
			switch(algorithm)
			{
				case ALGORITHM_DENSITY:
				case ALGORITHM_DENSITY_FILTER:
				case ALGORITHM_RDF:
				case ALGORITHM_AXIAL_DF:
				case ALGORITHM_LOCAL_CHEMISTRY:
				case ALGORITHM_LOCAL_CHEMISTRY_FILTER:
				{
					size_t ltmp=STOP_MODE_ENUM_END;

					for(unsigned int ui=0;ui<STOP_MODE_ENUM_END;ui++)
					{
						if(value == TRANS(STOP_MODES[ui]))
						{
							ltmp=ui;
							break;
						}
					}
					
					if(ltmp>=STOP_MODE_ENUM_END)
						return false;
					
					stopMode=ltmp;
					needUpdate=true;
					clearCache();
					break;
				}
				default:
					//Should know what algorithm we use.
					ASSERT(false);
				break;
			}
			break;
		}	
		case KEY_DISTMAX:
		{
			float ltmp;
			if(stream_cast(ltmp,value))
				return false;
			
			if(ltmp<= 0.0)
				return false;
			
			distMax=ltmp;
			needUpdate=true;
			clearCache();

			break;
		}	
		case KEY_NNMAX:
		{
			unsigned int ltmp;
			if(stream_cast(ltmp,value))
				return false;
		
			//NNmax should be nonzero at all times. For local chemistry
			// should be at least 2 (as 1 == 100% all the time)	
			if(ltmp==0 || ((algorithm == ALGORITHM_LOCAL_CHEMISTRY || algorithm == ALGORITHM_LOCAL_CHEMISTRY_FILTER)  && ltmp < 2))
				return false;
			
			nnMax=ltmp;
			needUpdate=true;
			clearCache();

			break;
		}	
		case KEY_NNMAX_NORMALISE:
		{
			if(!applyPropertyNow(normaliseNNHist,value,needUpdate))
				return false;
			break;
		}	
		case KEY_NNMAX_SHOWRANDOM:
		{
			if(!applyPropertyNow(wantRandomNNHist,value,needUpdate))
				return false;
			break;
		}	
		case KEY_NUMBINS:
		{
			unsigned int ltmp;
			if(stream_cast(ltmp,value))
				return false;
			
			if(ltmp==0)
				return false;
			
			numBins=ltmp;
			needUpdate=true;
			clearCache();

			break;
		}
		case KEY_REDUCTIONDIST:
		{
			float ltmp;
			if(stream_cast(ltmp,value))
				return false;
			
			if(ltmp<= 0.0)
				return false;
			
			reductionDistance=ltmp;
			needUpdate=true;
			clearCache();

			break;
		}	
		case KEY_REMOVAL:
		{
			if(!applyPropertyNow(excludeSurface,value,needUpdate))
				return false;
			break;
		}
		case KEY_NORMALISERDF:
		{
			if(!applyPropertyNow(normaliseRDF,value,needUpdate))
				return false;
			break;
		}
		case KEY_COLOUR:
		{
			ColourRGBA tmpRgba;

			if(!tmpRgba.parse(value))
				return false;

			
			if(rgba.toColourRGBA() != tmpRgba)
			{
				rgba=tmpRgba.toRGBAf();

				if(cacheOK)
				{
					for(size_t ui=0;ui<filterOutputs.size();ui++)
					{
						if(filterOutputs[ui]->getStreamType() == STREAM_TYPE_PLOT)
						{
							PlotStreamData *p;
							p =(PlotStreamData*)filterOutputs[ui];

							p->r=rgba.r();
							p->g=rgba.g();
							p->b=rgba.b();
						}
					}

				}

				needUpdate=true;
			}


			break;
		}
		case KEY_ENABLE_SOURCE_ALL:
		{
			ASSERT(haveRangeParent);
			bool allEnabled=true;
			for(unsigned int ui=0;ui<ionSourceEnabled.size();ui++)
			{
				if(!ionSourceEnabled[ui])
				{
					allEnabled=false;
					break;
				}
			}

			//Invert the result and assign
			allEnabled=!allEnabled;
			for(unsigned int ui=0;ui<ionSourceEnabled.size();ui++)
				ionSourceEnabled[ui]=allEnabled;

			needUpdate=true;
			clearCache();
			break;
		}
		case KEY_ENABLE_TARGET_ALL:
		{
			ASSERT(haveRangeParent);
			bool allEnabled=true;
			for(unsigned int ui=0;ui<ionNames.size();ui++)
			{
				if(!ionTargetEnabled[ui])
				{
					allEnabled=false;
					break;
				}
			}

			//Invert the result and assign
			allEnabled=!allEnabled;
			for(unsigned int ui=0;ui<ionNames.size();ui++)
				ionTargetEnabled[ui]=allEnabled;

			needUpdate=true;
			clearCache();
			break;
		}
		case KEY_ENABLE_NUMERATOR_ALL:
		{
			ASSERT(haveRangeParent);
			bool allEnabled=true;
			for(unsigned int ui=0;ui<ionNumeratorEnabled.size();ui++)
			{
				if(!ionNumeratorEnabled[ui])
				{
					allEnabled=false;
					break;
				}
			}

			//Invert the result and assign
			allEnabled=!allEnabled;
			for(unsigned int ui=0;ui<ionNumeratorEnabled.size();ui++)
				ionNumeratorEnabled[ui]=allEnabled;

			needUpdate=true;
			clearCache();
			break;
		}
		case KEY_CUTOFF:
		{
			string stripped=stripWhite(value);

			float ltmp;
			if(stream_cast(ltmp,stripped))
				return false;

			if(ltmp<= 0.0)
				return false;
		
			if(ltmp != densityCutoff)
			{	
				densityCutoff=ltmp;
				needUpdate=true;
				clearCache();
			}
			else
				needUpdate=false;
			break;
		}
		case KEY_RETAIN_UPPER:
		{
			if(!applyPropertyNow(keepDensityUpper,value,needUpdate))
				return false;
			break;
		}
		case KEY_RADIUS:
		{
			float newRad;
			if(stream_cast(newRad,value))
				return false;

			if(newRad < sqrtf(std::numeric_limits<float>::epsilon()))
				return false;

			if(scalarParams[0] != newRad )
			{
				scalarParams[0] = newRad;
				needUpdate=true;
				clearCache();
			}
			return true;
		}
		case KEY_NORMAL:
		{
			Point3D newPt;
			if(!newPt.parse(value))
				return false;

			if(newPt.sqrMag() < sqrtf(std::numeric_limits<float>::epsilon()))
				return false;

			if(!(vectorParams[0] == newPt ))
			{
				vectorParams[0] = newPt;
				needUpdate=true;
				clearCache();
			}


			if(algorithm == ALGORITHM_AXIAL_DF)
			{
				vectorParams[0].normalise();
			}

			return true;
		}
		case KEY_ORIGIN:
		{
			if(!applyPropertyNow(vectorParams[0],value,needUpdate))
				return false;
			return true;
		}
		case KEY_NUMIONS:
		{
			unsigned int ltmp;
			if(stream_cast(ltmp,value))
				return false;
			
			if(ltmp<=1)
				return false;
			
			numIonsSegment=ltmp;
			needUpdate=true;
			clearCache();

			break;
		}
		case KEY_SHOW_BINOM_FREQ:
		{
			if(!applyPropertyNow(showBinomialFrequencies,value,needUpdate))
				return false;
			break;
		}
		case KEY_SHOW_BINOM_NORM_FREQ:
		{
			if(!applyPropertyNow(showNormalisedBinomialFrequencies,value,needUpdate))
				return false;
			break;
		}
		case KEY_SHOW_BINOM_THEOR_FREQ:
		{
			if(!applyPropertyNow(showTheoreticFrequencies,value,needUpdate))
				return false;
			break;
		}
		case KEY_BINOMIAL_MAX_ASPECT:
		{
			float ltmp;
			if(stream_cast(ltmp,value))
				return false;
			
			if(ltmp<=1)
				return false;
			
			maxBlockAspect=ltmp;
			needUpdate=true;
			clearCache();

			break;
		}
		case KEY_BINOMIAL_EXTRUDE_DIR:
		{
			map<string,unsigned int> choices;
			choices["x"]=0;
			choices["y"]=1;
			choices["z"]=2;

			map<string,unsigned int>::iterator it;
			it=choices.find(value);
			
			if(it == choices.end())
				return false;
			
			extrusionDirection=it->second;
			needUpdate=true;
			clearCache();
			break;
		}
		case KEY_SHOW_BINOM_3D_GRID:
		{
			if(!applyPropertyNow(showGridOverlay,value,needUpdate))
				return false;

			break;
		}
		case KEY_REPLACE_FILE:
		{
			if(!applyPropertyNow(replaceFile,value,needUpdate))
				return false;
			break;
		}
		case KEY_REPLACE_TOLERANCE:
		{
			if(!applyPropertyNow(replaceTolerance,value,needUpdate))
				return false;
			break;
		}
		case KEY_REPLACE_ALGORITHM:
		{
			size_t newVal=REPLACE_MODE_ENUM_END;
			for(size_t ui=0;ui<REPLACE_MODE_ENUM_END; ui++)
			{
				if( value == TRANS(REPLACE_ALGORITHMS[ui]))
				{
					newVal=ui;
					break;
				}
			}
			if(newVal==REPLACE_MODE_ENUM_END)
				return false;

			if(replaceMode != newVal)
			{
				needUpdate=true;
				clearCache();
				replaceMode=newVal;
			}
			break;
		}
		case KEY_REPLACE_VALUE:
		{
			if(!applyPropertyNow(replaceMass,value,needUpdate))
				return false;
			break;
			
		}
		case KEY_ANGULARSTEP:
		{
			float ltmp;
			if(stream_cast(ltmp,value))
				return false;
			
			if(ltmp<=0)
				return false;
			
			dAngularStep=ltmp;
			needUpdate=true;
			clearCache();

			break;
		}
		case KEY_SHOW_DF_LOGSCALE:
		{
			bool newV;
			boolStrDec(value,newV);

			if(newV==wantDFLogScaled)
				return false;

			wantDFLogScaled=newV;
		
			//Modify the cache if we can, to avoid recomputing and invalidating the cache
			if(cacheOK)
			{
				//Find the 2D plot, and change its colour map in cache
				for(unsigned int ui=0;ui<filterOutputs.size();ui++)
				{
					if(filterOutputs[ui]->getStreamType() == STREAM_TYPE_PLOT2D)
					{
						Plot2DStreamData *p2D = (Plot2DStreamData*) filterOutputs[ui];

						p2D->wantImageLog=wantDFLogScaled;
						break;

					}
				}
			}
			needUpdate=true;

			break;
		}
		case KEY_DF_COLOURMAP:
		{
			unsigned int tmpMap;
			tmpMap=(unsigned int)-1;
			for(unsigned int ui=0;ui<COLOURMAP_ENUM_END;ui++)
			{
				if(value== getColourMapName(ui))
				{
					tmpMap=ui;
					break;
				}
			}

			if(tmpMap >=COLOURMAP_ENUM_END || tmpMap ==dfColourMap)
				return false;


			needUpdate=true;
			dfColourMap=tmpMap;

			if(cacheOK)
			{
				//Find the 2D plot, and change its colour map in cache
				for(unsigned int ui=0;ui<filterOutputs.size();ui++)
				{
					if(filterOutputs[ui]->getStreamType() == STREAM_TYPE_PLOT2D)
					{
						Plot2DStreamData *p2D = (Plot2DStreamData*) filterOutputs[ui];

						p2D->colourMap =dfColourMap;
						break;

					}
				}
			}
			break;
		}
	
		default:
		{
			ASSERT(haveRangeParent);
			//The incoming range keys are dynamically allocated to a 
			//position beyond any reasonable key. Its a hack,
			//but it works, and is entirely contained within the filter code.
			unsigned int ionOffset,keyType;
			demuxKey(key,keyType,ionOffset);

			bool doEnable;
			if(!boolStrDec(value,doEnable))
				return false;

			vector<bool> *vBool=0;
				
			switch(keyType)
			{
				case KEYTYPE_ENABLE_SOURCE:
					vBool=&ionSourceEnabled;
					break;
				case KEYTYPE_ENABLE_TARGET:
					vBool=&ionTargetEnabled;
					break;
				case KEYTYPE_ENABLE_NUMERATOR:
					vBool=&ionNumeratorEnabled;
					break;
				case KEYTYPE_ENABLE_DENOMINATOR:
					vBool=&ionDenominatorEnabled;
					break;
				default:	
					ASSERT(false);
			}
				
			if(vBool)
			{
				bool lastVal = (*vBool)[ionOffset]; 
				if(doEnable)
					(*vBool)[ionOffset]=true;
				else
					(*vBool)[ionOffset]=false;
				
				//if the result is different, the
				//cache should be invalidated
				if(lastVal!=(*vBool)[ionOffset])
				{
					needUpdate=true;
					clearCache();
				}
			}
		}

	}	
	return true;
}

std::string  SpatialAnalysisFilter::getSpecificErrString(unsigned int code) const
{
	const char *errStrings[] = {"",
				TRANS("Spatial analysis aborted by user"),
				TRANS("Insufficient memory for binomial. Reduce input size?"),
				TRANS("Required range data not present"), 
				TRANS("Unable to generate usable binomial grid"),

				TRANS("Insufficient points to continue"),
				TRANS("Unable to load file")
				};
	COMPILE_ASSERT(THREEDEP_ARRAYSIZE(errStrings) == SPAT_ERR_END_OF_ENUM);
	
	
	ASSERT(code < SPAT_ERR_END_OF_ENUM);

	return std::string(errStrings[code]);
}

void SpatialAnalysisFilter::setUserString(const std::string &str)
{
	//Which algorithms have plot outputs?
	const bool ALGORITHM_HAS_PLOTS[] = { false,false,true,true,true,false,false,false,true};

	COMPILE_ASSERT(THREEDEP_ARRAYSIZE(ALGORITHM_HAS_PLOTS) == ALGORITHM_ENUM_END);

	if(userString != str && ALGORITHM_HAS_PLOTS[algorithm])
	{
		userString=str;
		clearCache();
	}	
	else
		userString=str;
}

unsigned int SpatialAnalysisFilter::getRefreshBlockMask() const
{
	//Anything but ions and ranges can go through this filter.
	if(!WANT_RANGE_PROPAGATION[algorithm])
		return STREAM_TYPE_IONS | STREAM_TYPE_RANGE;
	else
		return STREAM_TYPE_IONS;

}

unsigned int SpatialAnalysisFilter::getRefreshEmitMask() const
{
	switch(algorithm)
	{
		case ALGORITHM_RDF:
			return STREAM_TYPE_IONS | STREAM_TYPE_PLOT;
		case ALGORITHM_BINOMIAL:
			return STREAM_TYPE_PLOT | STREAM_TYPE_DRAW;
		case ALGORITHM_AXIAL_DF:
			return STREAM_TYPE_PLOT ;
		case ALGORITHM_AXIAL_DF_SWEEP:
			return STREAM_TYPE_PLOT2D ;
		default:
			return STREAM_TYPE_IONS;
	}
}

unsigned int SpatialAnalysisFilter::getRefreshUseMask() const
{
	return STREAM_TYPE_IONS;
}

bool SpatialAnalysisFilter::writeState(std::ostream &f,unsigned int format, unsigned int depth) const
{
	using std::endl;
	switch(format)
	{
		case STATE_FORMAT_XML:
		{	
			f << tabs(depth) << "<" << trueName() << ">" << endl;
			f << tabs(depth+1) << "<userstring value=\""<< escapeXML(userString) << "\"/>"  << endl;
			f << tabs(depth+1) << "<algorithm value=\""<<algorithm<< "\"/>"  << endl;
			f << tabs(depth+1) << "<stopmode value=\""<<stopMode<< "\"/>"  << endl;
			f << tabs(depth+1) << "<nnmax value=\""<<nnMax<< "\"/>"  << endl;
			f << tabs(depth+1) << "<normalisennhist value=\""<<boolStrEnc(normaliseNNHist)<< "\"/>"  << endl;
			f << tabs(depth+1) << "<wantrandomnnhist value=\""<<boolStrEnc(wantRandomNNHist)<< "\"/>"  << endl;
			f << tabs(depth+1) << "<distmax value=\""<<distMax<< "\"/>"  << endl;
			f << tabs(depth+1) << "<numbins value=\""<<numBins<< "\"/>"  << endl;
			f << tabs(depth+1) << "<excludesurface value=\""<<excludeSurface<< "\"/>"  << endl;
			f << tabs(depth+1) << "<reductiondistance value=\""<<reductionDistance<< "\"/>"  << endl;
			f << tabs(depth+1) << "<normaliserdf value=\""<<boolStrEnc(normaliseRDF)<< "\"/>"  << endl;
			f << tabs(depth+1) << "<colour r=\"" <<  rgba.r() << "\" g=\"" << rgba.g() << "\" b=\"" <<rgba.b()
				<< "\" a=\"" << rgba.a() << "\"/>" <<endl;
			
			f << tabs(depth+1) << "<densitycutoff value=\""<<densityCutoff<< "\"/>"  << endl;
			f << tabs(depth+1) << "<keepdensityupper value=\""<<(int)keepDensityUpper<< "\"/>"  << endl;
			
			f << tabs(depth+1) << "<replace file=\""<<escapeXML(convertFileStringToCanonical(replaceFile)) << "\" mode=\"" << replaceMode 
				<< "\" tolerance=\"" << replaceTolerance <<  "\" replacemass=\"" << boolStrEnc(replaceMass) << "\" />"  << endl;


			//-- Binomial parameters ---
			f << tabs(depth+1) << "<binomial numions=\""<<numIonsSegment<< "\" maxblockaspect=\"" 
						<< maxBlockAspect << "\" extrusiondirection=\"" 
						<< extrusionDirection << "\"/>"  << endl;
			f << tabs(depth+1) << "<binomialdisplay freqs=\""<<(int)showBinomialFrequencies
						<< "\" normalisedfreqs=\"" << (int)showNormalisedBinomialFrequencies
						<< "\" theoreticfreqs=\""<< (int)showTheoreticFrequencies
						<< "\" gridoverlay=\""<< (int)showGridOverlay 
						<< "\"/>" << endl;
			f << tabs(depth+1) << "<axialdfsweep colourmap=\"" << dfColourMap << "\" angularstep=\"" << dAngularStep << "\" thetastart=\"" << angleStartStop[0][0] 
					<<"\" thetastop=\"" << angleStartStop[0][1] << "\" phistart=\"" << angleStartStop[1][0] << "\" phistop=\"" << angleStartStop[1][1] << 
					"\" logscale=\"" << boolStrEnc(wantDFLogScaled) << "\"/>" << endl;

						

			//--------------------------

			
			writeVectorsXML(f,"vectorparams",vectorParams,depth+1);
			writeScalarsXML(f,"scalarparams",scalarParams,depth+1);
		
			if(ionNames.size())	
			{
				writeIonsEnabledXML(f,"source",ionSourceEnabled,ionNames,depth+1);
				writeIonsEnabledXML(f,"target",ionTargetEnabled,ionNames,depth+1);
				writeIonsEnabledXML(f,"numerator",ionNumeratorEnabled,ionNames,depth+1);
				writeIonsEnabledXML(f,"denominator",ionDenominatorEnabled,ionNames,depth+1);
			}

			f << tabs(depth) << "</" << trueName() << ">" << endl;
			break;
		}
		default:
			ASSERT(false);
			return false;
	}

	return true;
}


void SpatialAnalysisFilter::getStateOverrides(std::vector<string> &externalAttribs) const 
{
	externalAttribs.push_back(replaceFile);

}

bool SpatialAnalysisFilter::writePackageState(std::ostream &f, unsigned int format,
			const std::vector<std::string> &valueOverrides, unsigned int depth) const
{
	ASSERT(valueOverrides.size() == 1);

	//Temporarily modify the state of the filter, then call writestate
	string tmpReplaceFile=replaceFile;


	//override const and self-modify
	// this is quite naughty, but we know what we are doing...
	const_cast<SpatialAnalysisFilter *>(this)->replaceFile=valueOverrides[0];
	bool result;
	result=writeState(f,format,depth);

	//restore the filter state, such that the caller doesn't notice that this has been modified
	const_cast<SpatialAnalysisFilter *>(this)->replaceFile=tmpReplaceFile;

	return result;
}

bool SpatialAnalysisFilter::readState(xmlNodePtr &nodePtr, const std::string &stateFileDir)
{
	using std::string;
	string tmpStr;

	//Retrieve user string
	//===
	if(XMLHelpFwdToElem(nodePtr,"userstring"))
		return false;

	xmlChar *xmlString=xmlGetProp(nodePtr,(const xmlChar *)"value");
	if(!xmlString)
		return false;
	userString=(char *)xmlString;
	xmlFree(xmlString);
	//===

	//Retrieve algorithm
	//====== 
	if(!XMLGetNextElemAttrib(nodePtr,algorithm,"algorithm","value"))
		return false;
	if(algorithm >=ALGORITHM_ENUM_END)
		return false;
	//===
	
	//Retrieve stop mode 
	//===
	if(!XMLGetNextElemAttrib(nodePtr,stopMode,"stopmode","value"))
		return false;
	if(stopMode >=STOP_MODE_ENUM_END)
		return false;
	//===
	
	//Retrieve nnMax val
	//====== 
	if(!XMLGetNextElemAttrib(nodePtr,nnMax,"nnmax","value"))
		return false;
	if(!nnMax)
		return false;
	//===
	
	//Retrieve histogram normalisation 
	//TODO: COMPAT : did not exist prior to 0.0.17
	// internal 5033191f0c61
	//====== 
	xmlNodePtr tmpNode = nodePtr;
	if(!XMLGetNextElemAttrib(tmpNode,normaliseNNHist,"normalisennhist","value"))
	{
		normaliseNNHist=false;
	}
	//===
	
	//Retrieve histogram normalisation 
	//TODO: COMPAT : did not exist prior to 0.0.18
	// internal revision : 2302dbbfb3dd 
	//====== 
	tmpNode = nodePtr;
	if(!XMLGetNextElemAttrib(tmpNode,wantRandomNNHist,"wantrandomnnhist","value"))
	{
		wantRandomNNHist=false;
	}
	//===
	
	//Retrieve distMax val
	//====== 
	if(!XMLGetNextElemAttrib(nodePtr,distMax,"distmax","value"))
		return false;
	if(distMax <=0.0)
		return false;
	//===
	
	//Retrieve numBins val
	//====== 
	if(!XMLGetNextElemAttrib(nodePtr,numBins,"numbins","value"))
		return false;
	if(!numBins)
		return false;
	//===
	
	//Retrieve exclude surface on/off
	//===
	if(!XMLGetNextElemAttrib(nodePtr,tmpStr,"excludesurface","value"))
		return false;
	//check that new value makes sense 
	if(!boolStrDec(tmpStr,excludeSurface))
		return false;
	//===
	

	//Get reduction distance
	//===
	if(!XMLGetNextElemAttrib(nodePtr,reductionDistance,"reductiondistance","value"))
		return false;
	if(reductionDistance < 0.0f)
		return false;
	//===

	//FIXME: COMPAT BREAK 3Depict <=0.0.20 does not contain this node
	//===
	tmpNode=nodePtr;
	if(!XMLGetNextElemAttrib(nodePtr,normaliseRDF,"normaliserdf","value"))
	{
		normaliseRDF=false;
		nodePtr=tmpNode;
	}
	//===

	//Retrieve colour
	//====
	if(XMLHelpFwdToElem(nodePtr,"colour"))
		return false;
	ColourRGBAf tmpRgbaf;
	if(!parseXMLColour(nodePtr,tmpRgbaf))
		return false;
	rgba=tmpRgbaf;
	//====


	//Retrieve density cutoff & upper 
	if(!XMLGetNextElemAttrib(nodePtr,densityCutoff,"densitycutoff","value"))
		return false;
	if(densityCutoff< 0.0f)
		return false;

	if(!XMLGetNextElemAttrib(nodePtr,tmpStr,"keepdensityupper","value"))
		return false;
	//check that new value makes sense 
	if(!boolStrDec(tmpStr,keepDensityUpper))
		return false;


	//FIXME:COMPAT_BREAK : 3Depict <= internal fb7d66397b7b does not contain
	tmpNode=nodePtr;
	if(!XMLHelpFwdToElem(nodePtr,"replace"))
	{
		if(XMLHelpGetProp(replaceFile,nodePtr,"file"))
			return false;

		//Convert the file from relative to abs as needed	
		convertFileStringToAbsolute(stateFileDir, replaceFile);
		//Convert to native format
		replaceFile=convertFileStringToNative(replaceFile);
		
	
		if(XMLHelpGetProp(replaceMode,nodePtr,"mode"))
			return false;
		
		if(replaceMode>REPLACE_MODE_ENUM_END)
			return false;
		
		if(XMLHelpGetProp(replaceTolerance,nodePtr,"tolerance"))
			return false;
		if(replaceTolerance < 0)
			return false;

		if(XMLHelpGetProp(replaceMass, nodePtr,"replacemass"))
			return false;
	}
	else
		nodePtr=tmpNode;

	//FIXME:COMPAT_BREAK : 3Depict <= 1796:5639f6d50732 does not contain
	// this section

	tmpNode=nodePtr;
	if(!XMLHelpFwdToElem(nodePtr,"binomial"))
	{
		unsigned int nSegment;
		float maxAspect;

		//Retrieve segmentation count
		if(!XMLGetAttrib(nodePtr,nSegment,"numions"))
			return false;
		if(nSegment <= 1)
			return false;
		numIonsSegment=nSegment;


		//Retrieve and verify aspect ratio
		if(!XMLGetAttrib(nodePtr,maxAspect,"maxblockaspect"))
			return false;

		if(maxAspect<1.0f)
			return false;
		maxBlockAspect=maxAspect;

		//Get the extrusion direction
		unsigned int tmpExtr;
		if(!XMLGetAttrib(nodePtr,tmpExtr,"extrusiondirection"))
			return false;

		if(tmpExtr >=3)
			return false;
		extrusionDirection=tmpExtr;


		//Spin to binomial display
		if(XMLHelpFwdToElem(nodePtr,"binomialdisplay"))
			return false;


		if(!XMLGetAttrib(nodePtr,tmpStr,"freqs"))
			return false;

		if(!boolStrDec(tmpStr,showBinomialFrequencies))
			return false;
		
		if(!XMLGetAttrib(nodePtr,tmpStr,"normalisedfreqs"))
			return false;
		
		if(!boolStrDec(tmpStr,showNormalisedBinomialFrequencies))
			return false;
		
		if(!XMLGetAttrib(nodePtr,tmpStr,"theoreticfreqs"))
			return false;
		
		if(!boolStrDec(tmpStr,showTheoreticFrequencies))
			return false;


	}
	else
		nodePtr=tmpNode;


	tmpNode=nodePtr;
	//FIXME:COMPAT_BREAK: Prior to 0.0.21, axialdf was not implemented
	if(!XMLHelpFwdToElem(nodePtr,"axialdfsweep"))
	{
		unsigned int cMap;

		//Retrieve colourmap id 
		if(!XMLGetAttrib(nodePtr,cMap,"colourmap"))
			return false;
		if(!cMap|| cMap > COLOURMAP_ENUM_END)
			return false;
		dfColourMap =cMap;


		//Retrieve and verify angular step
		float angleStep;
		if(!XMLGetAttrib(nodePtr,angleStep,"angularstep"))
			return false;
		if(angleStep<=0.0f || angleStep > 180)
			return false;
		dAngularStep=angleStep;

		//Get the angle start/stop
		float angleBounds[2][2];
		for(unsigned int ui=0;ui<4;ui++)
		{
			string var;
			if(!(ui/2))
				var="theta";
			else
				var="phi";

			if(ui%2)
				var+="stop";
			else
				var+="start";


			if(!XMLGetAttrib(nodePtr,angleBounds[ui/2][ui%2],var.c_str()))
				return false;

		}
		angleBounds[0][0]=std::min(fabs(angleBounds[0][0]),90.0f);
		angleBounds[0][1]=std::min(fabs(angleBounds[0][1]),90.0f);
		angleBounds[1][0]=std::min(fabs(angleBounds[1][0]),180.0f);
		angleBounds[1][1]=std::min(fabs(angleBounds[1][1]),180.0f);

		if(angleBounds[0][0] >=angleBounds[0][1])
			return false;
		if(angleBounds[1][0] >=angleBounds[1][1])
			return false;

		//Check that the step size matches the given bounds
		float delta;
		delta=angleBounds[0][1] - angleBounds[0][0];
		if(dAngularStep> delta )
			dAngularStep=delta;
		delta=angleBounds[1][1] - angleBounds[1][0];
		if(dAngularStep> delta )
			dAngularStep=delta;

		//Retrieve colourmap id 
		bool bLogScale;
		if(!XMLGetAttrib(nodePtr,bLogScale,"logscale"))
			return false;
		wantDFLogScaled =bLogScale;

	}
	else
		nodePtr=tmpNode;
	//FIXME: COMPAT_BREAK : Earlier versions of the state file <= 1441:adaa3a3daa80
	// do not contain this section, so we must be fault tolerant
	// when we bin backwards compatability, do this one too.

	tmpNode=nodePtr;
	if(!XMLHelpFwdToElem(nodePtr,"scalarparams"))
		readScalarsXML(nodePtr,scalarParams);
	else
		nodePtr=tmpNode;

	if(!XMLHelpFwdToElem(nodePtr,"vectorparams"))
		readVectorsXML(nodePtr,vectorParams);
	else
		nodePtr=tmpNode;

	//FIXME: Remap the ion names  we load from the file to the ion names that we 
	// see in the rangefile

	vector<string> ionNames;
	if(!XMLHelpFwdToElem(nodePtr,"source"))
		readIonsEnabledXML(nodePtr,ionSourceEnabled,ionNames);
	nodePtr=tmpNode;
	if(!XMLHelpFwdToElem(nodePtr,"target"))
		readIonsEnabledXML(nodePtr,ionTargetEnabled,ionNames);

	nodePtr=tmpNode;
	if(!XMLHelpFwdToElem(nodePtr,"numerator"))
		readIonsEnabledXML(nodePtr,ionNumeratorEnabled,ionNames);
	
	nodePtr=tmpNode;
	if(!XMLHelpFwdToElem(nodePtr,"denominator"))
		readIonsEnabledXML(nodePtr,ionDenominatorEnabled,ionNames);

	//FIXME: We need to check ionName sizes and Enabled vector sizes match
	// and should also check no duplicates in ionNames

	resetParamsAsNeeded();
	
	return true;
}

void SpatialAnalysisFilter::setPropFromBinding(const SelectionBinding &b)
{
	
	switch(b.getID())
	{
		case BINDING_CYLINDER_RADIUS:
			b.getValue(scalarParams[0]);
			break;
		case BINDING_CYLINDER_DIRECTION:
		{
			Point3D p;
			b.getValue(p);
			if(p.sqrMag() > sqrtf(std::numeric_limits<float>::epsilon()))
				vectorParams[1]=p;
			break;
		}
		case BINDING_CYLINDER_ORIGIN:
			b.getValue(vectorParams[0]);
			break;
		default:
			ASSERT(false);
	}

	clearCache();
}

void SpatialAnalysisFilter::resetParamsAsNeeded()
{
	//Perform any needed
	// transformations to internal vars
	switch(algorithm)
	{
		case ALGORITHM_AXIAL_DF:
		{
			if(vectorParams.size() !=1)
			{
				size_t oldSize=vectorParams.size();
				vectorParams.resize(1);
				if(oldSize== 0)
					vectorParams[0]=Point3D(0,0,1);
			}
			else
			{
				if(vectorParams[0].sqrMag() <=std::numeric_limits<float>::epsilon())
					vectorParams[0]= Point3D(0,0,1);
				else
					vectorParams[0].normalise();
			}

			if(scalarParams.size() !=1)
			{
				size_t oldSize=scalarParams.size();
				scalarParams.resize(1);
				if(!oldSize)
					scalarParams[0]=DEFAULT_AXIAL_DISTANCE;
			}
			break;
		}
		case ALGORITHM_DENSITY_FILTER:
		{
			if(scalarParams.size() !=1)
				scalarParams.resize(1,0);
		}
		default:
			//fall through
		;
	}
}

void SpatialAnalysisFilter::filterSelectedRanges(const vector<IonHit> &ions, bool sourceFilter, const RangeFile *rngF,
			vector<IonHit> &output) const
{
	//Remap ions from the list for this filter (which is a subset of range)
	// to complete list in rangefile
	vector<bool> rangeSourceEnabled, rangeTargetEnabled;
	rangeSourceEnabled=expandEnabledToParentRange(ionSourceEnabled,rngF);
	rangeTargetEnabled=expandEnabledToParentRange(ionTargetEnabled,rngF);

	if(sourceFilter)
		rngF->rangeByIon(ions,rangeSourceEnabled,output);
	else
		rngF->rangeByIon(ions,rangeTargetEnabled,output);
}

			
size_t SpatialAnalysisFilter::algorithmRDF(ProgressData &progress, size_t totalDataSize, 
		const vector<const FilterStreamData *>  &dataIn, 
		vector<const FilterStreamData * > &getOut,const RangeFile *rngF)
{
	progress.step=1;
	progress.stepName=TRANS("Collate");
	progress.filterProgress=0;
	
	if(*Filter::wantAbort)
		return FILTER_ERR_ABORT;

	K3DTree kdTree;
	
	//Source points
	vector<Point3D> p;
	bool needSplitting;

	needSplitting=false;
	//We only need to split up the data if we have to 
	if((size_t)std::count(ionSourceEnabled.begin(),ionSourceEnabled.end(),true)!=ionSourceEnabled.size()
		|| (size_t)std::count(ionTargetEnabled.begin(),ionTargetEnabled.end(),true)!=ionTargetEnabled.size() )
		needSplitting=true;

	if(haveRangeParent && needSplitting)
	{
		vector<Point3D> pts[2];
		ASSERT(ionNames.size());
		
		//Create a new bool vector that matches to the parent rangefile
		vector<bool> iseParent,iteParent;
		iseParent=expandEnabledToParentRange(ionSourceEnabled,rngF);
		iteParent=expandEnabledToParentRange(ionTargetEnabled,rngF);

		size_t errCode;
		if((errCode=buildSplitPoints(dataIn,progress,totalDataSize,
				rngF,iseParent, iteParent,pts[0],pts[1])))
			return errCode;

		progress.step=2;
		progress.stepName=TRANS("Build");

		//Build the tree using the target ions
		//(its roughly nlogn timing, but worst case n^2)
		kdTree.buildByRef(pts[1]);
		if(*Filter::wantAbort)
			return FILTER_ERR_ABORT;
		pts[1].clear();
		
		//Remove surface points from sources if desired
		if(excludeSurface)
		{
			ASSERT(reductionDistance > 0);
			progress.step++;
			progress.stepName=TRANS("Surface");

			if(*Filter::wantAbort)
				return FILTER_ERR_ABORT;


			//Take the input points, then use them
			//to compute the convex hull reduced 
			//volume. 
			vector<Point3D> returnPoints;
			errCode=GetReducedHullPts(pts[0],reductionDistance,
					&progress.filterProgress,*(Filter::wantAbort),
					returnPoints);
			if(errCode ==1)
				return INSUFFICIENT_SIZE_ERR;
			else if(errCode)
			{
				ASSERT(false);
				return FILTER_ERR_ABORT;
			}
			
			if(*Filter::wantAbort)
				return FILTER_ERR_ABORT;

			pts[0].clear();
			//Forget the original points, and use the new ones
			p.swap(returnPoints);
		}
		else
			p.swap(pts[0]);

	}
	else
	{
		unsigned int errCode; 
		if((errCode=Filter::collateIons(dataIn,p,progress,totalDataSize)))
			return errCode;
		
		progress.step=2;
		progress.stepName=TRANS("Build");
		BoundCube treeDomain;
		treeDomain.setBounds(p);

		//Build the tree (its roughly nlogn timing, but worst case n^2)
		kdTree.buildByRef(p);
		if(*Filter::wantAbort)
			return FILTER_ERR_ABORT;

		//Remove surface points if desired
		if(excludeSurface)
		{
			ASSERT(reductionDistance > 0);
			progress.step++;
			progress.stepName=TRANS("Surface");
		
			if(*Filter::wantAbort)
				return FILTER_ERR_ABORT;


			//Take the input points, then use them
			//to compute the convex hull reduced 
			//volume. 
			vector<Point3D> returnPoints;
			size_t errCode;
			if((errCode=GetReducedHullPts(p,reductionDistance,
					&progress.filterProgress, *Filter::wantAbort,
					returnPoints)) )
			{
				if(errCode ==1)
					return INSUFFICIENT_SIZE_ERR;
				else if(errCode ==2)
					return ERR_ABORT_FAIL;
				else
				{
					ASSERT(false);
					return ERR_ABORT_FAIL;
				}
			}



			//Forget the original points, and use the new ones
			p.swap(returnPoints);
			
			if(*Filter::wantAbort)
				return FILTER_ERR_ABORT;

		}
		
	}

	//Let us perform the desired analysis
	progress.step++;
	progress.stepName=TRANS("Analyse");

	//If there is no data, there is nothing to do.
	if(p.empty() || !kdTree.nodeCount())
		return	0;
	
	//OK, at this point, the KD tree contains the target points
	//of interest, and the vector "p" contains the source points
	//of interest, whatever they might be.
	switch(stopMode)
	{
		case STOP_MODE_NEIGHBOUR:
		{
			//User is after an NN histogram analysis

			//Histogram is output as a per-NN histogram of frequency.
			vector<vector<size_t> > histogram;
			
			//Bin widths for the NN histograms (each NN hist
			//is scaled separately). The +1 is due to the tail bin
			//being the totals
			float *binWidth = new float[nnMax];


			unsigned int errCode;
			//Run the analysis
			errCode=generateNNHist(p,kdTree,nnMax,
					numBins,histogram,binWidth,
					&(progress.filterProgress),*Filter::wantAbort);
			switch(errCode)
			{
				case 0:
					break;
				case RDF_ERR_INSUFFICIENT_INPUT_POINTS:
				{
					delete[] binWidth;
					return INSUFFICIENT_SIZE_ERR;
				}
				case RDF_ABORT_FAIL:
				{
					delete[] binWidth;
					return ERR_ABORT_FAIL;
				}
				default:
					ASSERT(false);
			}

		
			vector<vector<float> > histogramFloat;
			histogramFloat.resize(nnMax); 
			//Normalise the NN histograms to a per bin width as required
			for(unsigned int ui=0;ui<nnMax; ui++)
			{
				histogramFloat[ui].resize(numBins);
				if(normaliseNNHist)
				{
					for(unsigned int uj=0;uj<numBins;uj++)
						histogramFloat[ui][uj] = (float)histogram[ui][uj]/binWidth[ui] ;
				}
				else
				{
					for(unsigned int uj=0;uj<numBins;uj++)
						histogramFloat[ui][uj] = (float)histogram[ui][uj];
				}
			}
			histogram.clear();

	
			//Alright then, we have the histogram in x-{y1,y2,y3...y_n} form
			//lets make some plots shall we?
			{
			PlotStreamData *plotData[nnMax];

			for(unsigned int ui=0;ui<nnMax;ui++)
			{
				plotData[ui] = new PlotStreamData;
				plotData[ui]->index=ui;
				plotData[ui]->parent=this;
				plotData[ui]->plotMode=PLOT_MODE_1D;
				plotData[ui]->xLabel=TRANS("Radial Distance");
				if(normaliseNNHist)
					plotData[ui]->yLabel=TRANS("Count/Distance");
				else
				{
					plotData[ui]->yLabel=TRANS("Count");
					//Show y=0 region on plot
					plotData[ui]->setPlotBound(1,0,0);
				}
				std::string tmpStr;
				stream_cast(tmpStr,ui+1);
				plotData[ui]->dataLabel=getUserString() + string(" ") +tmpStr + TRANS("NN Freq.");

				//Red plot.
				plotData[ui]->r=rgba.r();
				plotData[ui]->g=rgba.g();
				plotData[ui]->b=rgba.b();
				plotData[ui]->xyData.resize(numBins);

				for(unsigned int uj=0;uj<numBins;uj++)
				{
					float dist;
					ASSERT(ui < histogramFloat.size() && uj<histogramFloat[ui].size());
					dist = (float)uj*binWidth[ui];
					plotData[ui]->xyData[uj] = std::make_pair(dist,
							histogramFloat[ui][uj]);
				}

				cacheAsNeeded(plotData[ui]);
				
				getOut.push_back(plotData[ui]);
			}
			}

			//If requested, add a probability distribution.
			// we need to scale it to match the displayed observed
			// histogram
			if(wantRandomNNHist)
			{
				vector<vector<float> > nnTheoHist;
				nnTheoHist.resize(nnMax);
				#pragma omp parallel for
				for(unsigned int ui=0;ui<nnMax;ui++)
				{

					float total=0;
					for(unsigned int uj=0;uj<numBins;uj++)
						total+=histogramFloat[ui][uj]*binWidth[ui];


					//Generate the eval points
					vector<float> evalDist;
					evalDist.resize(numBins);	
					for(unsigned int uj=0;uj<numBins;uj++)
						evalDist[uj] = (float)uj*binWidth[ui];

					//Compute the random Knn density parameter from the histogram
					//equation is from L. Stephenson PhD Thesis, Eq7.8, pp91, 2009,
					// Univ. Sydney.
					//--
					// gamma(3/2+1)^(1/3)
					const float GAMMA_FACTOR = 1.09954261650577;
					const float SQRT_PI = 1.77245385090552;
					

					float mean=weightedMean(evalDist,histogramFloat[ui]);
					
					float densNumerator, densDenom;
					densNumerator= gsl_sf_gamma( (ui+1) + 1.0/3.0);
					densNumerator*=GAMMA_FACTOR;
					densDenom=mean*SQRT_PI*gsl_sf_fact(ui);
					float density;
					density=densNumerator/densDenom;
					density=density*density*density; //Cubed

					//--
					//create the distribution
					generateKnnTheoreticalDist(evalDist,density, ui+1,nnTheoHist[ui]);

					//scale the dist
					for(size_t uj=0;uj<nnTheoHist[ui].size();uj++)
					{
						nnTheoHist[ui][uj]*= total;
					}

				}

				PlotStreamData *plotData[nnMax];
				for(unsigned int ui=0;ui<nnMax;ui++)
				{
					plotData[ui] = new PlotStreamData;
					plotData[ui]->index=ui+nnMax;
					plotData[ui]->parent=this;
					plotData[ui]->plotMode=PLOT_MODE_1D;
					plotData[ui]->xLabel=TRANS("Radial Distance");

//					plotData[ui]->lineStyle=LINE_STYLE_DASH;

					if(normaliseNNHist)
						plotData[ui]->yLabel=TRANS("Count/Distance");
					else
						plotData[ui]->yLabel=TRANS("Count");

					plotData[ui]->setPlotBound(1,0,0);
				
					std::string tmpStr;
					stream_cast(tmpStr,ui+1);
					plotData[ui]->dataLabel=getUserString() + string(" Random ") +tmpStr + TRANS("NN Freq.");

					//Red plot.
					plotData[ui]->r=rgba.r();
					plotData[ui]->g=rgba.g();
					plotData[ui]->b=rgba.b();
					plotData[ui]->xyData.resize(numBins);

					for(unsigned int uj=0;uj<numBins;uj++)
					{
						float dist;
						ASSERT(ui < histogramFloat.size() && uj<histogramFloat[ui].size());
						dist = (float)uj*binWidth[ui];
						plotData[ui]->xyData[uj] = std::make_pair(dist,
								nnTheoHist[ui][uj]);
					}

					cacheAsNeeded(plotData[ui]);
					
					getOut.push_back(plotData[ui]);
				}
			}
			delete[] binWidth;
			break;
		}
		case STOP_MODE_RADIUS:
		{
			unsigned int warnBiasCount=0;
			
			//Histogram is output as a histogram of frequency vs distance
			unsigned int *histogram = new unsigned int[numBins];
			for(unsigned int ui=0;ui<numBins;ui++)
				histogram[ui] =0;
		
			//User is after an RDF analysis. Run it.
			unsigned int errcode;
			errcode=generateDistHist(p,kdTree,histogram,distMax,numBins,
					warnBiasCount,&(progress.filterProgress),*(Filter::wantAbort));

			if(errcode)
				return ERR_ABORT_FAIL;

			if(warnBiasCount)
			{
				string sizeStr;
				stream_cast(sizeStr,warnBiasCount);
			
				appendConsoleMessage(std::string(TRANS("Warning, "))
						+ sizeStr + TRANS(" points were unable to find neighbour points that exceeded the search radius, and thus terminated prematurely"));
			}

			PlotStreamData *plotData = new PlotStreamData;

			plotData->plotMode=PLOT_MODE_1D;
			plotData->index=0;
			plotData->parent=this;
			plotData->xLabel=TRANS("Radial Distance");
			plotData->yLabel=TRANS("Count");
			plotData->dataLabel=getUserString() + TRANS(" RDF");
		
			//Show y=0 region on plot
			plotData->setPlotBound(1,0,0);

			plotData->r=rgba.r();
			plotData->g=rgba.g();
			plotData->b=rgba.b();
			plotData->xyData.resize(numBins);


			if(!normaliseRDF)
			{
				for(unsigned int uj=0;uj<numBins;uj++)
				{
					float dist;
					dist = (float)uj/(float)numBins*distMax;
					plotData->xyData[uj] = std::make_pair(dist,
							histogram[uj]);
				}
			}
			else
			{
				vector<float> density,weightVal;
				density.resize(numBins);
				weightVal.resize(numBins);

				//Compute density of each shell
				float distPlusCube,distMinusCube;
				distMinusCube=0;
				for(unsigned int uj=0;uj<density.size();uj++)
				{
					distPlusCube=(float)(uj+1)/(float)numBins*distMax;
					distPlusCube*=distPlusCube*distPlusCube;

					density[uj] = histogram[uj]/ (4.0/3.0*M_PI*(distPlusCube - distMinusCube));
					distMinusCube =distPlusCube;
				}

				//Construct weight vector
				for(unsigned int uj=0;uj<numBins; uj++)
					weightVal[uj]=histogram[uj];
			
				//Compute weighted mean
				float meanDensity=vectorWeightedMean(density,weightVal);

				distMinusCube=0;
				float distMinus=0;
				for(unsigned int uj=0;uj<numBins;uj++)
				{
					float distPlus;
					distPlus=(float)(uj+1)/(float)numBins*distMax;
					distPlusCube=distPlus*distPlus*distPlus;


					float nExpected;
					nExpected = meanDensity*(4.0/3.0*M_PI*(distPlusCube - distMinusCube));
					
					float distCentre;
					distCentre =0.5*(distPlus+ distMinus);
					plotData->xyData[uj] = std::make_pair(distCentre,
							histogram[uj]/nExpected);
					
					distMinusCube =distPlusCube;
					distMinus=distPlus;
				}



			}


			delete[] histogram;
			
			cacheAsNeeded(plotData);
			
			getOut.push_back(plotData);

			//Propagate input streams as desired, propagate everything but the 
			// ion inputs
			size_t mask=STREAM_TYPE_IONS;
			if(!WANT_RANGE_PROPAGATION[algorithm])
				mask|=STREAM_TYPE_RANGE;
			propagateStreams(dataIn,getOut,mask,true);

			break;
		}
		default:
			ASSERT(false);
	}

	return 0;
}

size_t SpatialAnalysisFilter::algorithmDensity(ProgressData &progress, 
	size_t totalDataSize, const vector<const FilterStreamData *>  &dataIn, 
		vector<const FilterStreamData * > &getOut)
{
	vector<Point3D> p;
	size_t errCode;
	progress.step=1;
	progress.stepName=TRANS("Collate");
	if((errCode=Filter::collateIons(dataIn,p,progress,totalDataSize)))
		return errCode;

	progress.step=2;
	progress.stepName=TRANS("Build");
	progress.filterProgress=0;
	if(*Filter::wantAbort)
		return FILTER_ERR_ABORT;

	BoundCube treeDomain;
	treeDomain.setBounds(p);

	//Build the tree (its roughly nlogn timing, but worst case n^2)
	K3DTreeMk2 kdTree;
	kdTree.resetPts(p);
	if(!kdTree.build())
		return FILTER_ERR_ABORT;
					
	if(kdTree.size() < nnMax)
	{
		string s;
		s = TRANS("Number of NNs requested was more than the number of points in dataset - this won't make sense.\n");
		s+=TRANS("Tree has only :");
		string tmp;
		stream_cast(tmp,kdTree.size());
		s+=tmp;
		s+=TRANS(" points");

		appendConsoleMessage(s);
	}

	if(*Filter::wantAbort)
		return FILTER_ERR_ABORT;

	//Its algorithm time!
	//----
	//Update progress stuff
	size_t n=0;
	progress.step=3;
	progress.stepName=TRANS("Analyse");
	progress.filterProgress=0;
	if(*Filter::wantAbort)
		return FILTER_ERR_ABORT;

	//List of points for which there was a failure
	//first entry is the point Id, second is the 
	//dataset id.
	std::list<std::pair<size_t,size_t> > badPts;
	for(size_t ui=0;ui<dataIn.size() ;ui++)
	{
		std::list<std::pair<size_t,size_t> > curBadPts;
		switch(dataIn[ui]->getStreamType())
		{
			case STREAM_TYPE_IONS: 
			{
				const IonStreamData *d;
				d=((const IonStreamData *)dataIn[ui]);
				IonStreamData *newD = new IonStreamData;
				newD->parent=this;

				//Adjust this number to provide more update than usual, because we
				//are not doing an o(1) task between updates; yes, it is a hack
				unsigned int curProg=NUM_CALLBACK/(10*nnMax);
				newD->data.resize(d->data.size());
				if(stopMode == STOP_MODE_NEIGHBOUR)
				{
					unsigned int nProg=0;

					bool spin=false;
					#pragma omp parallel for shared(spin)
					for(size_t uj=0;uj<d->data.size();uj++)
					{
						if(spin)
							continue;
						Point3D r;
						vector<size_t> res;
						r=d->data[uj].getPosRef();
						
						//Assign the mass to charge using nn density estimates
						kdTree.findKNearestUntagged(r,treeDomain,nnMax+1,res);
						
						float maxSqrRad;
						maxSqrRad=0;

						//Get the radius as the furthest object
						if(res.size() > 1)
						{
							for(unsigned int ui=0;ui<res.size();ui++)
							{
								float sqrDist;
								//Find the square distance to the points in the tree
								// and keep the maximum
								sqrDist=kdTree.getPt(res[ui])->sqrDist(r);
								maxSqrRad= std::max(maxSqrRad,sqrDist);
							}
						}

						const float TOL = sqrt(std::numeric_limits<float>::epsilon());
						if(res.size() > 1 && maxSqrRad > TOL)
						{	

							//Set the mass as the volume of sphere * the number of NN
							// we have to subtract 1 to account for the initial search pt
							newD->data[uj].setMassToCharge((res.size()-1)/(4.0/3.0*M_PI*powf(maxSqrRad,3.0/2.0)));
							//Keep original position
							newD->data[uj].setPos(r);
						}
						else
						{
							//Should not have only 1 result (point itself), if there is more than one point in the tree
							ASSERT(res.size() >1 || kdTree.size() == 1);

							//Point was too close  to the other - density cannot be defined stably
							#pragma omp critical
							curBadPts.emplace_back(uj,ui);
						}

						res.clear();
						#pragma atomic
						nProg++;
						
						//Update progress as needed
						if(!curProg--)
						{
							#pragma omp critical 
							{
							progress.filterProgress= (unsigned int)(((float)nProg/(float)totalDataSize)*100.0f);
							if(*Filter::wantAbort)
								spin=true;
							curProg=NUM_CALLBACK/(nnMax);
							}
						}
					}

					if(spin)
					{
						delete newD;
						return ERR_ABORT_FAIL;
					}

					//move any bad points from the array to the end, then drop them
					//To do this, we have to reverse sort the array, then
					//swap the output ion vector entries with the end,
					//then do a resize.
					ComparePairFirst cmp;
					curBadPts.sort(cmp);
					curBadPts.reverse();

					//Do some swappage
					size_t pos=1;
					for(std::list<std::pair<size_t,size_t> >::iterator it=curBadPts.begin(); it!=curBadPts.end();++it)
					{
						newD->data[(*it).first]=newD->data[newD->data.size()-pos];
						pos++;
					}

					//Trim the tail of bad points, leaving only good points
					newD->data.resize(newD->data.size()-curBadPts.size());

					badPts.splice(badPts.begin(),curBadPts);
					ASSERT(curBadPts.empty());

				}
				else if(stopMode == STOP_MODE_RADIUS)
				{
#ifdef _OPENMP
					bool spin=false;
#endif
					float vol = 4.0/3.0*M_PI*distMax*distMax*distMax; //Sphere volume=4/3 Pi R^3
					#pragma omp parallel for shared(spin) firstprivate(treeDomain,curProg)
					for(size_t uj=0;uj<d->data.size();uj++)
					{
#ifdef _OPENMP
						if(spin)
							continue;
#endif
						
						Point3D r;
						vector<size_t> pts;	
						r=d->data[uj].getPosRef();

						//Locate the points in the area
						kdTree.ptsInSphere(r,distMax,pts);
						
						//Update progress as needed
						if(!curProg--)
						{
#pragma omp critical
							{
							progress.filterProgress= (unsigned int)((float)n/(float)totalDataSize*100.0f);
							if(*Filter::wantAbort)
							{
#ifdef _OPENMP
								spin=true;
#else
								delete newD;
								return ERR_ABORT_FAIL;
#endif
							}
							}
							curProg=NUM_CALLBACK/(10*nnMax);
						}
						
						n++;
						ASSERT(pts.size());

						//Set the mass as the volume of sphere * the number of NN
						// note that the central point eneds to be excluded (-1)
						newD->data[uj].setMassToCharge((pts.size()-1)/vol);
						//Keep original position
						newD->data[uj].setPos(r);

						pts.clear();
						
					}

#ifdef _OPENMP
					if(spin)
					{
						delete newD;
						return ERR_ABORT_FAIL;
					}
#endif
				}
				else
				{
					//Should not get here.
					ASSERT(false);
				}




				if(newD->data.size())
				{
					//Use default colours
					newD->r=d->r;
					newD->g=d->g;
					newD->b=d->b;
					newD->a=d->a;
					newD->ionSize=d->ionSize;
					newD->valueType=TRANS("Number Density (\\#/Vol^3)");

					//Cache result as neede
					cacheAsNeeded(newD);
					getOut.push_back(newD);
				}
				else
					delete newD;
			}
			break;	
			case STREAM_TYPE_RANGE: 
			break;
			default:
				getOut.push_back(dataIn[ui]);
				break;
		}
	}

	progress.filterProgress=100;

	//If we have bad points, let the user know.
	if(!badPts.empty())
	{
		std::string sizeStr;
		stream_cast(sizeStr,badPts.size());
		appendConsoleMessage(TRANS("Found ") + sizeStr + TRANS(" points with too-close neighbours (< tolerance)- these have been dropped"));

		//Print out a list of points if we can

		size_t maxPrintoutSize=std::min(badPts.size(),(size_t)200);
		list<pair<size_t,size_t> >::iterator it;
		it=badPts.begin();
		while(maxPrintoutSize--)
		{
			std::string s;
			const IonStreamData *d;
			d=((const IonStreamData *)dataIn[it->second]);

			Point3D getPos;
			getPos=	d->data[it->first].getPosRef();
			stream_cast(s,getPos);
			appendConsoleMessage(s);
			++it;
		}

		if(badPts.size() > 200)
		{
			appendConsoleMessage(TRANS("And so on..."));
		}


	}	
	
	return 0;
}

size_t SpatialAnalysisFilter::algorithmDensityFilter(ProgressData &progress, 
		size_t totalDataSize, const vector<const FilterStreamData *>  &dataIn, 
		vector<const FilterStreamData * > &getOut)
{
	vector<Point3D> p;
	size_t errCode;
	progress.step=1;
	progress.stepName=TRANS("Collate");
	if((errCode=Filter::collateIons(dataIn,p,progress,totalDataSize)))
		return errCode;

	progress.step=2;
	progress.stepName=TRANS("Build");
	progress.filterProgress=0;
	if(*Filter::wantAbort)
		return FILTER_ERR_ABORT;

	BoundCube treeDomain;
	treeDomain.setBounds(p);

	//Build the tree (its roughly nlogn timing, but worst case n^2)
	K3DTree kdTree;
	kdTree.buildByRef(p);


	//Update progress 
	if(*Filter::wantAbort)
		return FILTER_ERR_ABORT;
	p.clear(); //We don't need pts any more, as tree *is* a copy.


	//Its algorithm time!
	//----
	//Update progress stuff
	size_t n=0;
	progress.step=3;
	progress.stepName=TRANS("Analyse");
	progress.filterProgress=0;
	if(*Filter::wantAbort)
		return FILTER_ERR_ABORT;

	//List of points for which there was a failure
	//first entry is the point Id, second is the 
	//dataset id.
	std::list<std::pair<size_t,size_t> > badPts;
	for(size_t ui=0;ui<dataIn.size() ;ui++)
	{
		switch(dataIn[ui]->getStreamType())
		{
			case STREAM_TYPE_IONS: 
			{
				const IonStreamData *d;
				d=((const IonStreamData *)dataIn[ui]);
				IonStreamData *newD = new IonStreamData;
				newD->parent=this;

				//Adjust this number to provide more update than usual, because we
				//are not doing an o(1) task between updates; yes, it is a hack
				const unsigned int PROG_PER_PASS=NUM_CALLBACK/(10*nnMax);
				unsigned int curProg=PROG_PER_PASS;
				newD->data.reserve(d->data.size());
				if(stopMode == STOP_MODE_NEIGHBOUR)
				{
					bool spin=false;
					#pragma omp parallel for shared(spin)
					for(size_t uj=0;uj<d->data.size();uj++)
					{
						if(spin)
							continue;
						Point3D r;
						vector<const Point3D *> res;
						r=d->data[uj].getPosRef();
						
						//Assign the mass to charge using nn density estimates
						kdTree.findKNearest(r,treeDomain,nnMax,res);

						if(res.size())
						{	
							float maxSqrRad;

							//Get the radius as the furthest object
							maxSqrRad= (res[res.size()-1]->sqrDist(r));


							float density;
							density = res.size()/(4.0/3.0*M_PI*powf(maxSqrRad,3.0/2.0));

							if(xorFunc((density <=densityCutoff), keepDensityUpper))
							{
#pragma omp critical
								newD->data.push_back(d->data[uj]);
							}

						}
						else
						{
							#pragma omp critical
							badPts.emplace_back(uj,ui);
						}

						res.clear();
						
						//Update progress as needed
						if(!curProg--)
						{
							#pragma omp critical 
							{
							n+=PROG_PER_PASS;
							progress.filterProgress= (unsigned int)(((float)n/(float)totalDataSize)*100.0f);
							if(*Filter::wantAbort)
								spin=true;
							curProg=PROG_PER_PASS;
							}
						}
					}

					if(spin)
					{
						delete newD;
						return ERR_ABORT_FAIL;
					}


				}
				else if(stopMode == STOP_MODE_RADIUS)
				{
#ifdef _OPENMP
					bool spin=false;
#endif
					float maxSqrRad = distMax*distMax;
					float vol = 4.0/3.0*M_PI*maxSqrRad*distMax; //Sphere volume=4/3 Pi R^3
					#pragma omp parallel for shared(spin) firstprivate(treeDomain,curProg)
					for(size_t uj=0;uj<d->data.size();uj++)
					{
						Point3D r;
						const Point3D *res;
						float deadDistSqr;
						unsigned int numInRad;
#ifdef _OPENMP
						if(spin)
							continue;
#endif	
						r=d->data[uj].getPosRef();
						numInRad=0;
						deadDistSqr=0;

						//Assign the mass to charge using nn density estimates
						do
						{
							res=kdTree.findNearest(r,treeDomain,deadDistSqr);

							//Check to see if we found something
							if(!res)
							{
#pragma omp critical
								badPts.emplace_back(uj, ui);
								break;
							}
							
							if(res->sqrDist(r) >maxSqrRad)
								break;
							numInRad++;

							ASSERT(numInRad < kdTree.nodeCount());
							//Advance ever so slightly beyond the next ion
							deadDistSqr = res->sqrDist(r)+std::numeric_limits<float>::epsilon();
							//Update progress as needed
							if(!curProg--)
							{
#pragma omp critical
								{
								progress.filterProgress= (unsigned int)((float)n/(float)totalDataSize*100.0f);
								if(*Filter::wantAbort)
								{
#ifdef _OPENMP
									spin=true;
#else
									delete newD;
									return ERR_ABORT_FAIL;
#endif
								}
								}
#ifdef _OPENMP
								if(spin)
									break;
#endif
								curProg=NUM_CALLBACK/(10*nnMax);
							}
						}while(true);
						
						n++;
						float density;
						density = numInRad/vol;

						if(xorFunc((density <=densityCutoff), keepDensityUpper))
						{
#pragma omp critical
							newD->data.push_back(d->data[uj]);
						}
						
					}

#ifdef _OPENMP
					if(spin)
					{
						delete newD;
						return ERR_ABORT_FAIL;
					}
#endif
				}
				else
				{
					//Should not get here.
					ASSERT(false);
				}


				//move any bad points from the array to the end, then drop them
				//To do this, we have to reverse sort the array, then
				//swap the output ion vector entries with the end,
				//then do a resize.
				ComparePairFirst cmp;
				badPts.sort(cmp);
				badPts.reverse();

				//Do some swappage
				size_t pos=1;
				for(std::list<std::pair<size_t,size_t> >::iterator it=badPts.begin(); it!=badPts.end();++it)
				{
					newD->data[(*it).first]=newD->data[newD->data.size()-pos];
				}

				//Trim the tail of bad points, leaving only good points
				newD->data.resize(newD->data.size()-badPts.size());


				if(newD->data.size())
				{
					//Use default colours
					newD->r=d->r;
					newD->g=d->g;
					newD->b=d->b;
					newD->a=d->a;
					newD->ionSize=d->ionSize;
					newD->valueType=TRANS("Number Density (\\#/Vol^3)");

					//Cache result as needed
					cacheAsNeeded(newD);
					getOut.push_back(newD);
				}
				else
					delete newD;
			}
			break;	
			default:
				getOut.push_back(dataIn[ui]);
				break;
		}
	}
	progress.filterProgress=100;

	//If we have bad points, let the user know.
	if(!badPts.empty())
	{
		std::string sizeStr;
		stream_cast(sizeStr,badPts.size());
		appendConsoleMessage(std::string(TRANS("Warning,")) + sizeStr + 
				TRANS(" points were un-analysable. These have been dropped"));

		//Print out a list of points if we can

		size_t maxPrintoutSize=std::min(badPts.size(),(size_t)200);
		list<pair<size_t,size_t> >::iterator it;
		it=badPts.begin();
		while(maxPrintoutSize--)
		{
			std::string s;
			const IonStreamData *d;
			d=((const IonStreamData *)dataIn[it->second]);

			Point3D getPos;
			getPos=	d->data[it->first].getPosRef();
			stream_cast(s,getPos);
			appendConsoleMessage(s);
			++it;
		}

		if(badPts.size() > 200)
		{
			appendConsoleMessage(TRANS("And so on..."));
		}


	}	
	
	return 0;
}


size_t SpatialAnalysisFilter::algorithmAxialDf(ProgressData &progress, 
		size_t totalDataSize, const vector<const FilterStreamData *>  &dataIn, 
		vector<const FilterStreamData * > &getOut,const RangeFile *rngF)
{
	//Need bins to perform histogram
	ASSERT(numBins);

	//Collate all the ions into a single array
	//--
	progress.step=1;
	progress.stepName=TRANS("Collate");
	progress.filterProgress=0;

	vector<IonHit> collatedIons;
	collateIons(dataIn,collatedIons,progress);
	//--


	//Build the KD tree
	//--
	progress.step=2;
	progress.stepName=TRANS("Build");
	progress.filterProgress=0;
	
	K3DTreeMk2 tree;
	tree.resetPts(collatedIons,false);
	tree.build();
	if(*Filter::wantAbort)
		return FILTER_ERR_ABORT;
	//--

	//Create an array of source points
	//--
	//FIXME:  Allow for range selection
	vector<Point3D> srcPoints;
	srcPoints.resize(collatedIons.size());
#pragma omp parallel for
	for(unsigned int ui=0;ui<srcPoints.size();ui++)
		srcPoints[ui]=collatedIons[ui].getPosRef();

	//--

	progress.step=3;
	progress.stepName=TRANS("Compute");
	progress.filterProgress=0;
	unsigned int *histogram = new unsigned int[numBins];
	for(unsigned int ui=0;ui<numBins;ui++)
		histogram[ui] =0;

	float binWidth;
	//OK, so now we have two datasets that need to be analysed. Lets do it
	unsigned int errCode;

	bool histOK=false;
	switch(stopMode)
	{
		case STOP_MODE_NEIGHBOUR:
		{
			Point3D axisNormal=vectorParams[0];
			axisNormal.normalise();

			errCode=generate1DAxialNNHist(srcPoints ,tree,axisNormal, histogram,
					binWidth,nnMax,numBins,&progress.filterProgress,
					*Filter::wantAbort);

			break;
		}
		case STOP_MODE_RADIUS:
		{
			Point3D axisNormal=vectorParams[0];
			axisNormal.normalise();

			errCode=generate1DAxialDistHist(srcPoints, tree,axisNormal, histogram,
					distMax,numBins,&progress.filterProgress,*Filter::wantAbort);

			histOK = (errCode ==0);
			break;
		}
		default:
			ASSERT(false);
	}
	
	//Remap the underlying function code to that for this function
	switch(errCode)
	{
		case 0:
			histOK=true;
			break;
		case RDF_ERR_INSUFFICIENT_INPUT_POINTS:
			appendConsoleMessage(TRANS("Insufficient points to complete analysis"));
			errCode=0;
			break;
		case RDF_ABORT_FAIL:
			errCode=ERR_ABORT_FAIL;
			break;
		default:
			ASSERT(false);
	}

	if(errCode)
	{
		delete[] histogram;
		return errCode;
	}

	if(histOK)
	{
		PlotStreamData *plotData = new PlotStreamData;

		plotData->plotMode=PLOT_MODE_1D;
		plotData->index=0;
		plotData->parent=this;
		plotData->xLabel=TRANS("Axial Distance");
		plotData->yLabel=TRANS("Count");
		plotData->dataLabel=getUserString() + TRANS(" 1D Dist. Func.");

		plotData->setPlotBound(1,0,0);

		plotData->r=rgba.r();
		plotData->g=rgba.g();
		plotData->b=rgba.b();
		plotData->xyData.resize(numBins);

		for(unsigned int uj=0;uj<numBins;uj++)
		{
			float dist;
			switch(stopMode)
			{
				case STOP_MODE_RADIUS:
					dist = ((float)uj - (float)numBins/2.0f)/(float)numBins*distMax*2.0;
					break;
				case STOP_MODE_NEIGHBOUR:
					dist= (float)uj*binWidth;
					break;
				default:
					ASSERT(false);
			}
			plotData->xyData[uj] = std::make_pair(dist,
					histogram[uj]);
		}

		cacheAsNeeded(plotData);
		getOut.push_back(plotData);
	}

	delete[] histogram;

	return 0;
}


size_t SpatialAnalysisFilter::algorithmAxialDfSweep(ProgressData &progress,
		size_t totalDataSize, const vector<const FilterStreamData *>  &dataIn, 
		vector<const FilterStreamData * > &getOut)
{

	vector<Point3D> pointList;
	vector<vector<vector<unsigned int > > > histogram;

	size_t errCode;
	progress.step=1;
	progress.stepName=TRANS("Collate");
	if((errCode=Filter::collateIons(dataIn,pointList,progress,totalDataSize)))
		return errCode;
	

	progress.step=2;
	progress.stepName=TRANS("Build");
	progress.filterProgress=0;
	if(*Filter::wantAbort)
		return FILTER_ERR_ABORT;

	K3DTreeMk2 tree;
	tree.resetPts(pointList,false);
	tree.build();
	if(*Filter::wantAbort)
		return FILTER_ERR_ABORT;


	progress.step=3;
	progress.stepName=TRANS("Sweep");

	//FIXME: Make user parameter
	const float NUM_BINS=1024;
	float dAngularStepRad = dAngularStep * M_PI/180.0f;
	unsigned int numStepsTheta= 0.5*M_PI/dAngularStepRad;
	unsigned int numStepsPhi= 2.0*M_PI/dAngularStepRad;
	histogram.resize(numStepsTheta);
	for(unsigned int ui=0;ui<histogram.size();ui++)
	{
		histogram[ui].resize(numStepsPhi);
		for(unsigned int uj=0;uj<histogram[ui].size();uj++)
			histogram[ui][uj].resize(NUM_BINS,0);
	}

	//FIXME: UNCOMMENT ME
	//Abort if the histogram step size is invalid
	//if (histogram.empty() || histogram[ui].empty())
	//	return FILTER_ERR_ANGULAR_STEP_TOO_LARGE;

	//Generate the 2D axial DF sweep
	//This generates all the DFs in each vector direction
	//TODO: specify angles
	float thetaStart,thetaStop,phiStart,phiStop;
	thetaStart=angleStartStop[0][0]*M_PI/180;
	thetaStop=angleStartStop[0][1]*M_PI/180;
	phiStart=angleStartStop[1][0]*M_PI/180;
	phiStop=angleStartStop[1][1]*M_PI/180;
	errCode=generate1DAxialDistHistSweep(pointList, tree,distMax, 
			progress.filterProgress,*Filter::wantAbort,histogram,
			thetaStart,thetaStop,phiStart,phiStop);

	if(errCode || *Filter::wantAbort)
		return ERR_ABORT_FAIL;


	Array2D<float> intensity;
	errorFitAxialDistArray(histogram,intensity);


	Plot2DStreamData *plot2D = new Plot2DStreamData;
	plot2D->plotStyle = PLOT_2D_DENS;
	plot2D->dataLabel = TRANS("Axial DF power");
	plot2D->xLabel = TRANS("Theta - Azimuthal angle (deg)");
	plot2D->yLabel = TRANS("Phi - Elevation angle (deg)");
	if(wantDFLogScaled)
		plot2D->wantImageLog=true;
	plot2D->colourMap=dfColourMap;

	plot2D->xyData = intensity;
	plot2D->xMin=0; 
	plot2D->yMin=0; 
	plot2D->xMax = 0.5*180;
	plot2D->yMax = 2.0*180;
	plot2D->parent=this;
	cacheAsNeeded(plot2D);
		
	getOut.push_back(plot2D);

	return 0;
}

size_t SpatialAnalysisFilter::algorithmBinomial(ProgressData &progress, 
		size_t totalDataSize, const vector<const FilterStreamData *>  &dataIn, 
		vector<const FilterStreamData * > &getOut,const RangeFile *rngF)
{
	vector<IonHit> ions;

	progress.step=1;
	progress.stepName=TRANS("Collate");
	progress.filterProgress=0;

	//Merge the ions form the incoming streams
	if(Filter::collateIons(dataIn,ions,progress,totalDataSize))
		return FILTER_ERR_ABORT;

	//Tell user we are on next step
	progress.step++;
	progress.stepName=TRANS("Binomial");
	progress.filterProgress=0;

	size_t errCode;

	SEGMENT_OPTION segmentOpts;

	//Set binomial parameters, including block
	// generation method (currently only AUTO_BRICK)
	segmentOpts.nIons=numIonsSegment;
	segmentOpts.strategy=BINOMIAL_SEGMENT_AUTO_BRICK;
	segmentOpts.extrusionDirection=extrusionDirection;
	segmentOpts.extrudeMaxRatio=maxBlockAspect;

	vector<GRID_ENTRY> gridEntries;

	//Map source enabled list to parent rangefile
	vector<bool> iseParent;
	iseParent=ionSourceEnabled;
	iseParent=expandEnabledToParentRange(iseParent,rngF);
	//work out which ions we enabled
	vector<size_t> selectedIons;
	for(size_t ui=0;ui<iseParent.size();ui++)
	{
		if(iseParent[ui])
			selectedIons.push_back(ui);
	}

	//generate the blocks
	errCode=countBinnedIons(ions,rngF,selectedIons,segmentOpts,gridEntries);

	switch(errCode)
	{
		case 0:
			break;
		case BINOMIAL_NO_MEM:
			return ERR_BINOMIAL_NO_MEM;
		default:
			ASSERT(false);
			return SPAT_ERR_END_OF_ENUM;
	}




	//Vector of ion frequencies in histogram of segment counts, 
	// each element in vector is for each ion type
	BINOMIAL_HIST binHist;
	genBinomialHistogram(gridEntries,selectedIons.size(),binHist);

	//If the histogram is empty, we cannot do any more
	if(!gridEntries.size())
		return ERR_BINOMIAL_BIN_FAIL;

	//If the user wants to see the overlaid grid, show this
	if(showGridOverlay)
	{
		DrawStreamData *draw=new DrawStreamData;
		draw->parent=this;

		for(size_t ui=0;ui<gridEntries.size();ui++)
		{
			DrawRectPrism *dR = new DrawRectPrism;

			dR->setAxisAligned(gridEntries[ui].startPt,
						gridEntries[ui].endPt);
			dR->setColour(0.0f,1.0f,0.0f,1.0f);
			dR->setLineWidth(2);

			draw->drawables.push_back(dR);
		}
		
		draw->cached=1;
		filterOutputs.push_back(draw);

		getOut.push_back(draw);
	}

	BINOMIAL_STATS binStats;
	computeBinomialStats(gridEntries,binHist,selectedIons.size(),binStats);

	//Show binomial statistics
	appendConsoleMessage(" ------ Binomial statistics ------");
	string tmpStr;
	stream_cast(tmpStr,gridEntries.size());
	appendConsoleMessage(string("Block count:\t") + tmpStr);
	appendConsoleMessage("Name\t\tMean\t\tChiSquare\t\tP_rand\t\tmu");
	for(size_t ui=0;ui<binStats.mean.size();ui++)
	{
		string lineStr;
		lineStr=rngF->getName(selectedIons[ui]) + std::string("\t\t");

		if(!binStats.pValueOK[ui])
		{
			lineStr+="\t\t Not computable ";
			appendConsoleMessage(lineStr);
			continue;
		}
		
		stream_cast(tmpStr,binStats.mean[ui]);
		lineStr+=tmpStr + string("\t\t");

		stream_cast(tmpStr,binStats.chiSquare[ui]);
		lineStr+=tmpStr + string("\t\t");

		stream_cast(tmpStr,binStats.pValue[ui]);
		lineStr+=tmpStr + string("\t\t");

		stream_cast(tmpStr,binStats.comparisonCoeff[ui]);
		lineStr+=tmpStr ;

		appendConsoleMessage(lineStr);

	}
	appendConsoleMessage(" ---------------------------------");


	ASSERT(binHist.mapIonFrequencies.size() ==
			binHist.normalisedFrequencies.size());

	if(!showBinomialFrequencies)
		return 0;


	//Show the experimental frequency information
	for(size_t ui=0;ui<binHist.mapIonFrequencies.size(); ui++)
	{
		if(binHist.mapIonFrequencies[ui].empty())
			continue;

		//Create a plot for this range
		PlotStreamData* plt;
		plt = new PlotStreamData;
		plt->index=ui;
		plt->parent=this;
		plt->plotMode=PLOT_MODE_1D;
		plt->plotStyle=PLOT_LINE_STEM;
		plt->xLabel=TRANS("Ions in Block");
		if(showNormalisedBinomialFrequencies)
			plt->yLabel=TRANS("Probability");
		else
			plt->yLabel=TRANS("Count");

		//Show y=0 region on plot
		plt->setPlotBound(1,0,0);

		//set the title
		string ionName;
		ionName+=rngF->getName(selectedIons[ui]);
		plt->dataLabel = string("Binomial:") + ionName;

		//Set the colour to match that of the range
		RGBf colour;	
		colour=rngF->getColour(selectedIons[ui]);
				
		plt->r=colour.red;
		plt->g=colour.green;
		plt->b=colour.blue;
		plt->xyData.resize(binHist.mapIonFrequencies[ui].size());

		size_t offset=0;
		if(showNormalisedBinomialFrequencies)
		{
			for(map<unsigned int, double>::const_iterator it=binHist.normalisedFrequencies[ui].begin();
					it!=binHist.normalisedFrequencies[ui].end();++it)
			{
				plt->xyData[offset]=std::make_pair(it->first,it->second);
				offset++;
			}
		}
		else
		{
			for(map<unsigned int, unsigned int >::const_iterator it=binHist.mapIonFrequencies[ui].begin();
					it!=binHist.mapIonFrequencies[ui].end();++it)
			{
				plt->xyData[offset]=std::make_pair(it->first,it->second);
				offset++;
			}
		}

		cacheAsNeeded(plt);
		getOut.push_back(plt);

	}

	if(!showTheoreticFrequencies)
		return 0;

	vector<std::map<unsigned int, double> > *mapTarget;
	if(showNormalisedBinomialFrequencies)
		mapTarget = &binHist.theoreticNormalisedFrequencies;
	else
		mapTarget = &binHist.theoreticFrequencies;
	for(size_t ui=0;ui<mapTarget->size(); ui++)
	{
		if(binHist.theoreticFrequencies[ui].empty())
			continue;

		//Create a plot for this range
		PlotStreamData* plt;
		plt = new PlotStreamData;
		plt->index=ui + binHist.mapIonFrequencies.size();
		plt->parent=this;
		plt->plotMode=PLOT_MODE_1D;
		plt->plotStyle=PLOT_LINE_STEPS;
		plt->xLabel=TRANS("Ions in Block");
		if(showNormalisedBinomialFrequencies)
			plt->yLabel=TRANS("Probability");
		else
			plt->yLabel=TRANS("Count (blocks)");
		
		//Show y=0 region on plot
		plt->setPlotBound(1,0,0);

		//set the title
		string ionName;
		ionName+=rngF->getName(selectedIons[ui]);
		plt->dataLabel = string("Binomial (theory):") + ionName;

		//Set the colour to match that of the range
		RGBf colour;	
		colour=rngF->getColour(selectedIons[ui]);
				
		plt->r=colour.red;
		plt->g=colour.green;
		plt->b=colour.blue;
		plt->xyData.resize((*mapTarget)[ui].size());

		//for each species, record the number of ions
		size_t offset=0;
		for(map<unsigned int, double>::const_iterator it=(*mapTarget)[ui].begin();
				it!=(*mapTarget)[ui].end();++it)
		{
			plt->xyData[offset]=std::make_pair(it->first,it->second);
			offset++;
		}

		cacheAsNeeded(plt);

		getOut.push_back(plt);

	}
	
	return 0;
}


size_t SpatialAnalysisFilter::algorithmLocalConcentration(ProgressData &progress, 
		size_t totalDataSize, const vector<const FilterStreamData *>  &dataIn, 
		vector<const FilterStreamData * > &getOut,const RangeFile *rngF)
{


	vector<IonHit> pSource;

#ifdef _OPENMP
	bool spin=false;	
#endif
	if(stopMode == STOP_MODE_RADIUS)
	{
		vector<Point3D> numeratorPts,denominatorPts;

		progress.step=1;
		progress.stepName=TRANS("Collate");
		progress.filterProgress=0;

		//Create a new bool vector that matches to the parent rangefile
		//===
		vector<bool> ineParent,ideParent,iseParent;
		ineParent=expandEnabledToParentRange(ionNumeratorEnabled,rngF);
		ideParent=expandEnabledToParentRange(ionDenominatorEnabled,rngF);
		iseParent=expandEnabledToParentRange(ionSourceEnabled,rngF);
		//===
	
		//Build the numerator and denominator points
		unsigned int errCode;
		errCode = buildSplitPoints(dataIn, progress, totalDataSize, rngF, 
				ineParent,ideParent,numeratorPts,denominatorPts);
		if(errCode)
			return errCode;	

		if(*Filter::wantAbort)
			return ERR_ABORT_FAIL;
		progress.step=2;
		progress.stepName = TRANS("Build Numerator");
		progress.filterProgress=0;


		//Build the tree (its roughly nlogn timing, but worst case n^2)
		K3DTreeMk2 treeNumerator,treeDenominator;
		treeNumerator.resetPts(numeratorPts);
		if(*Filter::wantAbort)
			return ERR_ABORT_FAIL;
		treeNumerator.build();
		if(*Filter::wantAbort)
			return ERR_ABORT_FAIL;

		progress.step=3;
		progress.stepName = TRANS("Build Denominator");
		progress.filterProgress=0;

		treeDenominator.resetPts(denominatorPts);
		treeDenominator.build();
		if(*Filter::wantAbort)
			return ERR_ABORT_FAIL;

		//TODO :Refactor this routine, counts number o fions
		// that are enabled. It gets used multiple times
		unsigned int sizeNeeded=0;
		//Count the array size that we need to store the points 
		for(unsigned int ui=0; ui<dataIn.size() ; ui++)
		{
			switch(dataIn[ui]->getStreamType())
			{
				case STREAM_TYPE_IONS:
				{
					const IonStreamData *d;
					d=((const IonStreamData *)dataIn[ui]);
					unsigned int ionID;
					ionID=getIonstreamIonID(d,rngF);

					//Check to see if we have a grouped set of ions
					if(ionID == (unsigned int)-1)
					{
						//we have ungrouped ions, so work out size individually
						for(unsigned int uj=0;uj<d->data.size();uj++)
						{
							ionID = rngF->getIonID(d->data[uj].getMassToCharge());
							if(ionID != (unsigned int)-1 && iseParent[ionID])
								sizeNeeded++;
						}
						break;
					}
					
					if(iseParent[ionID])
						sizeNeeded+=d->data.size();
				}
			}

		}

		pSource.resize(sizeNeeded);
		

		//Build the array of output points
		//--
		size_t curOffset=0;	
		for(unsigned int ui=0; ui<dataIn.size() ; ui++)
		{
			switch(dataIn[ui]->getStreamType())
			{
				case STREAM_TYPE_IONS:
				{
					unsigned int ionID;
					const IonStreamData *d;
					d=((const IonStreamData *)dataIn[ui]);
					ionID=getIonstreamIonID(d,rngF);

					if(ionID==(unsigned int)(-1))
					{
						//we have ungrouped ions, so work out size individually
						for(unsigned int uj=0;uj<d->data.size();uj++)
						{
							ionID = rngF->getIonID(d->data[uj].getMassToCharge());
							if(ionID != (unsigned int)-1 && iseParent[ionID])
							{
								pSource[curOffset] = d->data[uj];
								curOffset++;
							}
						}
						break;
					}

					if(iseParent[ionID])
					{
						std::copy(d->data.begin(),d->data.end(),pSource.begin()+curOffset);
						curOffset+=d->data.size();
					}

					break;
				}
				default:
					break;
			}

			if(*Filter::wantAbort)
				return ERR_ABORT_FAIL;
		}

		ASSERT(curOffset == pSource.size());
		//--

		progress.step=4;
		progress.stepName = TRANS("Compute");
		progress.filterProgress=0;

		//Loop through the array, and perform local search on each tree
#pragma omp parallel for schedule(dynamic)
		for(unsigned int ui=0;ui<pSource.size(); ui++)
		{
#ifdef _OPENMP
			if(spin)
				continue;
#endif

			vector<size_t> ptsNum,ptsDenom;
			//Find the points that are within the search radius
			treeNumerator.ptsInSphere(pSource[ui].getPosRef(),distMax,ptsNum);
			treeDenominator.ptsInSphere(pSource[ui].getPosRef(),distMax,ptsDenom);

			//Check to see if there is any self-matching going on. Don't allow zero-distance matches
			// as this biases the composition towards the chosen source points
			//TODO: Is there a faster way to do this? We might be able to track the original index of the point
			// that we built, and map it back to the input?
			//--
			unsigned int nCount,dCount;
			nCount=0;
			for(unsigned int uj=0;uj<ptsNum.size(); uj++)
			{
				size_t ptIdx;
				ptIdx=ptsNum[uj];
				float dist;
				dist = treeNumerator.getPtRef(ptIdx).sqrDist(pSource[ui].getPosRef());
				if(dist > DISTANCE_EPSILON)
					nCount++;
			}

			dCount=0;
			for(unsigned int uj=0;uj<ptsDenom.size(); uj++)
			{
				size_t ptIdx;
				ptIdx=ptsDenom[uj];
				float dist;
				dist = treeDenominator.getPtRef(ptIdx).sqrDist(pSource[ui].getPosRef());
				if(dist> DISTANCE_EPSILON)
					dCount++;
			}
			//--
		
			
			if( dCount )
				pSource[ui].setMassToCharge((float)nCount/(float)(dCount)*100.0f);
			else
				pSource[ui].setMassToCharge(-1.0f);
			

#ifdef _OPENMP 
			#pragma omp critical
			if(!omp_get_thread_num())
			{
#endif
				//let master thread do update	
				progress.filterProgress= (unsigned int)((float)ui/(float)pSource.size()*100.0f);

				if(*Filter::wantAbort)
				{
#ifndef _OPENMP
					return ERR_ABORT_FAIL;
#else
					#pragma atomic
					spin=true;
#endif			
				}
#ifdef _OPENMP
			}
#endif

			
		}
	}
	else if(stopMode == STOP_MODE_NEIGHBOUR)
	{

		//Merge the numerator and denominator ions into a single search tree
		vector<bool> enabledSearchIons;
		enabledSearchIons.resize(rngF->getNumIons());
		
		for(unsigned int ui=0;ui<enabledSearchIons.size(); ui++)	
		{
			enabledSearchIons[ui] = (ionNumeratorEnabled[ui] 
						|| ionDenominatorEnabled[ui]); 
		}	

		
		progress.step=1;
		progress.stepName=TRANS("Collate");
		progress.filterProgress=0;
	
		vector<IonHit> pTarget;

		//Create a new bool vector that matches to the parent rangefile
		vector<bool> iseParent,esiParent;
		iseParent=expandEnabledToParentRange(ionSourceEnabled,rngF);
		esiParent=expandEnabledToParentRange(enabledSearchIons,rngF);

		//FIXME: This is highly memory inefficient - 
		//	we build points, then throw them awaway.
		// We should build and range at the same time
		//===
		buildSplitPoints(dataIn,progress,totalDataSize,rngF,
					iseParent,esiParent,pSource, pTarget);
		//===

		if(*Filter::wantAbort)
			return ERR_ABORT_FAIL;

		if(pTarget.size() < nnMax)
			return INSUFFICIENT_SIZE_ERR;

		progress.step=2;
		progress.stepName=TRANS("Build");
		progress.filterProgress=0;

		//Keep a copy of the mass to charge data
		vector<float> dataMasses;
		dataMasses.resize(pTarget.size());
		#pragma omp parallel for
		for(unsigned int ui=0;ui<pTarget.size();ui++)
			dataMasses[ui]=pTarget[ui].getMassToCharge();

		K3DTreeMk2 searchTree;
		searchTree.resetPts(pTarget);
		searchTree.build();
		if(*Filter::wantAbort)
			return ERR_ABORT_FAIL;

		progress.step=3;
		progress.stepName=TRANS("Compute");
		progress.filterProgress=0;


		//Loop through the array, and perform local search on each tree
		BoundCube bc;
		searchTree.getBoundCube(bc);

#pragma omp parallel for schedule(dynamic) 
		for(unsigned int ui=0;ui<pSource.size(); ui++)
		{
#ifdef _OPENMP
			//If user requests abort, then do not process any more
			if(spin)
				continue;
#endif
			set<size_t> ptsFound;

			//Points from the tree we have already found. Abort if we cannot find enough NNs to satisfy search
			while(ptsFound.size()<nnMax)
			{
				size_t ptIdx;
				ptIdx=searchTree.findNearestWithSkip(pSource[ui].getPosRef(),bc,ptsFound);

				//Check that we have a valid NN
				if(ptIdx == (size_t)-1)
				{
					ptsFound.clear();
					break;
				}

				//distance between search pt and found pt

				ptsFound.insert(ptIdx);
			}


			unsigned int nCount;
			unsigned int dCount;
			nCount=dCount=0;	
			//Count the number of numerator and denominator ions, using the masses we set aside earlier
			for(set<size_t>::iterator it=ptsFound.begin(); it!=ptsFound.end(); ++it)
			{

				//check that the distance is non-zero, to force no self-matching
				float sqrDistance;
				sqrDistance = searchTree.getPtRef(*it).sqrDist(pSource[ui].getPosRef());
				if(sqrDistance < DISTANCE_EPSILON)
					continue;

				float ionMass;
				ionMass = dataMasses[searchTree.getOrigIndex(*it)];

				unsigned int ionID;
				ionID = rngF->getIonID(ionMass);


				//Ion can be either numerator or denominator OR BOTH.
				if(ionNumeratorEnabled[ionID])
					nCount++;
				if(ionDenominatorEnabled[ionID])
					dCount++;
			}

			//compute concentration
			pSource[ui].setMassToCharge((float)nCount/(float)(dCount)*100.0f);

#ifdef _OPENMP 
			if(!omp_get_thread_num())
			{
#endif
				//let master thread do update	
				progress.filterProgress= (unsigned int)((float)ui/(float)pSource.size()*100.0f);
				if(*Filter::wantAbort)
				{
#ifndef _OPENMP
					return ERR_ABORT_FAIL;
#else
					#pragma atomic
					spin=true;
#endif			
				}

#ifdef _OPENMP
			}
#endif

			
		}
	
	}
	else
	{
		//Should not get here...
		ASSERT(false);
		return ERR_ABORT_FAIL;
	}


#ifdef _OPENMP
	if(spin)
	{
		ASSERT(*Filter::wantAbort);
		return ERR_ABORT_FAIL;
	}
#endif
	progress.filterProgress=100;

	if(pSource.size())
	{
		IonStreamData *outData = new IonStreamData(this);
		//make a guess as to desired size/colour
		outData->estimateIonParameters(dataIn);
		//override colour to grey
		outData->g = outData->b = outData->r = 0.5;
		outData->valueType = TRANS("Relative Conc. (%)");
		outData->data.swap(pSource);
		cacheAsNeeded(outData);

		getOut.push_back(outData);
	}


	//Propagate input streams as desired, propagate everything but the 
	// ion inputs
	size_t mask=STREAM_TYPE_IONS;
	if(!WANT_RANGE_PROPAGATION[algorithm])
		mask|=STREAM_TYPE_RANGE;
	propagateStreams(dataIn,getOut,mask,true);

	return 0;
}



size_t SpatialAnalysisFilter::algorithmLocalConcentrationFilter(ProgressData &progress, 
		size_t totalDataSize, const vector<const FilterStreamData *>  &dataIn, 
		vector<const FilterStreamData * > &getOut,const RangeFile *rngF)
{


	vector<IonHit> pSource;
	
	vector<IonHit> pFiltered;


	if(stopMode == STOP_MODE_RADIUS)
	{
		vector<Point3D> numeratorPts,denominatorPts;

		progress.step=1;
		progress.stepName=TRANS("Collate");
		progress.filterProgress=0;

		//Create a new bool vector that matches to the parent rangefile
		//===
		vector<bool> ineParent,ideParent,iseParent;
		ineParent=expandEnabledToParentRange(ionNumeratorEnabled,rngF);
		ideParent=expandEnabledToParentRange(ionDenominatorEnabled,rngF);
		iseParent=expandEnabledToParentRange(ionSourceEnabled,rngF);
		//===
	
		//Build the numerator and denominator points
		unsigned int errCode;
		errCode = buildSplitPoints(dataIn, progress, totalDataSize, rngF, 
				ineParent,ideParent,numeratorPts,denominatorPts);
		if(errCode)
			return errCode;	

		if(*Filter::wantAbort)
			return ERR_ABORT_FAIL;
		progress.step=2;
		progress.stepName = TRANS("Build Numerator");
		progress.filterProgress=0;


		//Build the tree (its roughly nlogn timing, but worst case n^2)
		K3DTreeMk2 treeNumerator,treeDenominator;
		treeNumerator.resetPts(numeratorPts);
		if(*Filter::wantAbort)
			return ERR_ABORT_FAIL;
		treeNumerator.build();
		if(*Filter::wantAbort)
			return ERR_ABORT_FAIL;

		progress.step=3;
		progress.stepName = TRANS("Build Denominator");
		progress.filterProgress=0;

		treeDenominator.resetPts(denominatorPts);
		treeDenominator.build();
		if(*Filter::wantAbort)
			return ERR_ABORT_FAIL;

		//TODO :Refactor this routine, counts number o fions
		// that are enabled. It gets used multiple times
		unsigned int sizeNeeded=0;
		//Count the array size that we need to store the points 
		for(unsigned int ui=0; ui<dataIn.size() ; ui++)
		{
			switch(dataIn[ui]->getStreamType())
			{
				case STREAM_TYPE_IONS:
				{
					const IonStreamData *d;
					d=((const IonStreamData *)dataIn[ui]);
					unsigned int ionID;
					ionID=getIonstreamIonID(d,rngF);

					//Check to see if we have a grouped set of ions
					if(ionID == (unsigned int)-1)
					{
						//we have ungrouped ions, so work out size individually
						#pragma omp parallel for reduction(+:sizeNeeded)
						for(unsigned int uj=0;uj<d->data.size();uj++)
						{
							ionID = rngF->getIonID(d->data[uj].getMassToCharge());
							if(ionID != (unsigned int)-1 && iseParent[ionID])
								sizeNeeded++;
						}
						break;
					}
					
					if(iseParent[ionID])
						sizeNeeded+=d->data.size();
				}
			}

		}

		pSource.resize(sizeNeeded);
		

		//Build the array of output points
		//--
		size_t curOffset=0;	
		for(unsigned int ui=0; ui<dataIn.size() ; ui++)
		{
			switch(dataIn[ui]->getStreamType())
			{
				case STREAM_TYPE_IONS:
				{
					unsigned int ionID;
					const IonStreamData *d;
					d=((const IonStreamData *)dataIn[ui]);
					ionID=getIonstreamIonID(d,rngF);

					if(ionID==(unsigned int)(-1))
					{
						//we have ungrouped ions, so work out size individually
						for(unsigned int uj=0;uj<d->data.size();uj++)
						{
							ionID = rngF->getIonID(d->data[uj].getMassToCharge());
							if(ionID != (unsigned int)-1 && iseParent[ionID])
							{
								pSource[curOffset] = d->data[uj];
								curOffset++;
							}
						}
						break;
					}

					if(iseParent[ionID])
					{
						std::copy(d->data.begin(),d->data.end(),pSource.begin()+curOffset);
						curOffset+=d->data.size();
					}

					break;
				}
				default:
					break;
			}

			if(*Filter::wantAbort)
				return ERR_ABORT_FAIL;
		}

		ASSERT(curOffset == pSource.size());
		//--

		progress.step=4;
		progress.stepName = TRANS("Compute");
		progress.filterProgress=0;

#ifdef _OPENMP
		bool spin=false;
#endif
		//use dynamic scheduling to ensure progress is updated in a reasonable manner 
		#pragma omp parallel for  shared(pFiltered,spin) schedule(dynamic)
		//Loop through the array, and perform local search on each tree
		for(unsigned int ui=0;ui<pSource.size(); ui++)
		{
#ifdef _OPENMP
			if(spin)
				continue;
#endif

			vector<size_t> ptsNum,ptsDenom;
			//Find the points that are within the search radius
			treeNumerator.ptsInSphere(pSource[ui].getPosRef(),distMax,ptsNum);
			treeDenominator.ptsInSphere(pSource[ui].getPosRef(),distMax,ptsDenom);

			//Check to see if there is any self-matching going on. Don't allow zero-distance matches
			// as this biases the composition towards the chosen source points
			//TODO: Is there a faster way to do this? We might be able to track the original index of the point
			// that we built, and map it back to the input?
			//--
			unsigned int nCount,dCount;
			nCount=0;
			for(unsigned int uj=0;uj<ptsNum.size(); uj++)
			{
				size_t ptIdx;
				ptIdx=ptsNum[uj];
				float dist;
				dist = treeNumerator.getPtRef(ptIdx).sqrDist(pSource[ui].getPosRef());
				if(dist > DISTANCE_EPSILON)
					nCount++;
			}

			dCount=0;
			for(unsigned int uj=0;uj<ptsDenom.size(); uj++)
			{
				size_t ptIdx;
				ptIdx=ptsDenom[uj];
				float dist;
				dist = treeDenominator.getPtRef(ptIdx).sqrDist(pSource[ui].getPosRef());
				if(dist> DISTANCE_EPSILON)
					dCount++;
			}
			//--
		
			
			if( dCount )
			{
				if (xorFunc((float)nCount/(float)(dCount)*100.0f <= densityCutoff, keepDensityUpper))
				{
					pFiltered.push_back(pSource[ui]);
				}
			}
			


			//let master thread do update	
#ifdef _OPENMP 
			if(!omp_get_thread_num())
			{
#endif
				progress.filterProgress= (unsigned int)((float)ui/(float)pSource.size()*100.0f);
#ifdef _OPENMP
			}
#endif
			if(*Filter::wantAbort)
			{
#ifdef _OPENMP
				spin=true;
#else
				return ERR_ABORT_FAIL;
#endif

			}
			
		}
	}
	else if(stopMode == STOP_MODE_NEIGHBOUR)
	{

		//Merge the numerator and denominator ions into a single search tree
		vector<bool> enabledSearchIons;
		enabledSearchIons.resize(rngF->getNumIons());
		
		for(unsigned int ui=0;ui<enabledSearchIons.size(); ui++)	
		{
			enabledSearchIons[ui] = (ionNumeratorEnabled[ui] 
						|| ionDenominatorEnabled[ui]); 
		}	

		
		progress.step=1;
		progress.stepName=TRANS("Collate");
		progress.filterProgress=0;
	
		vector<IonHit> pTarget;

		//Create a new bool vector that matches to the parent rangefile
		vector<bool> iseParent,esiParent;
		iseParent=expandEnabledToParentRange(ionSourceEnabled,rngF);
		esiParent=expandEnabledToParentRange(enabledSearchIons,rngF);

		//FIXME: This is highly memory inefficient - 
		//	we build points, then throw them awaway.
		// We should build and range at the same time
		//===
		buildSplitPoints(dataIn,progress,totalDataSize,rngF,
					iseParent,esiParent,pSource, pTarget);
		//===

		if(*Filter::wantAbort)
			return ERR_ABORT_FAIL;

		if(pTarget.size() < nnMax)
			return INSUFFICIENT_SIZE_ERR;

		progress.step=2;
		progress.stepName=TRANS("Build");
		progress.filterProgress=0;

		//Keep a copy of the mass to charge data
		vector<float> dataMasses;
		dataMasses.resize(pTarget.size());
		#pragma omp parallel for
		for(unsigned int ui=0;ui<pTarget.size();ui++)
			dataMasses[ui]=pTarget[ui].getMassToCharge();

		K3DTreeMk2 searchTree;
		searchTree.resetPts(pTarget);
		searchTree.build();
		if(*Filter::wantAbort)
			return ERR_ABORT_FAIL;

		progress.step=3;
		progress.stepName=TRANS("Compute");
		progress.filterProgress=0;


		//Loop through the array, and perform local search on each tree
		BoundCube bc;
		searchTree.getBoundCube(bc);


#ifdef _OPENMP
		bool spin=false;
#endif
		#pragma omp parallel for  shared(pFiltered,spin) schedule(dynamic)
		for(unsigned int ui=0;ui<pSource.size(); ui++)
		{
#ifdef _OPENMP
			if(spin)
				continue;
#endif

			set<size_t> ptsFound;

			//Points from the tree we have already found. Abort if we cannot find enough NNs to satisfy search
			while(ptsFound.size()<nnMax)
			{
				size_t ptIdx;
				ptIdx=searchTree.findNearestWithSkip(pSource[ui].getPosRef(),bc,ptsFound);

				//Check that we have a valid NN
				if(ptIdx == (size_t)-1)
				{
					ptsFound.clear();
					break;
				}

				//distance between search pt and found pt

				ptsFound.insert(ptIdx);
			}


			unsigned int nCount;
			unsigned int dCount;
			nCount=dCount=0;	
			//Count the number of numerator and denominator ions, using the masses we set aside earlier
			for(set<size_t>::iterator it=ptsFound.begin(); it!=ptsFound.end(); ++it)
			{

				//check that the distance is non-zero, to force no self-matching
				float sqrDistance;
				sqrDistance = searchTree.getPtRef(*it).sqrDist(pSource[ui].getPosRef());
				if(sqrDistance < DISTANCE_EPSILON)
					continue;

				float ionMass;
				ionMass = dataMasses[searchTree.getOrigIndex(*it)];

				unsigned int ionID;
				ionID = rngF->getIonID(ionMass);


				//Ion can be either numerator or denominator OR BOTH.
				if(ionNumeratorEnabled[ionID])
					nCount++;
				if(ionDenominatorEnabled[ionID])
					dCount++;
			}

			//compute concentration
			if (dCount && xorFunc((float)nCount/(float)(dCount)*100.0f <= densityCutoff, keepDensityUpper))
			{
#pragma omp critical
				pFiltered.push_back(pSource[ui]);
			}

#ifdef _OPENMP 
			if(!omp_get_thread_num())
			{
#endif
				progress.filterProgress= (unsigned int)((float)ui/(float)pSource.size()*100.0f);
#ifdef _OPENMP
			}
#endif

			if(*Filter::wantAbort)
			{
#ifdef _OPENMP
				spin=true;
#else
				return ERR_ABORT_FAIL;
#endif
			}
		
		}
	
	}
	else
	{
		//Should not get here...
		ASSERT(false);
		return ERR_ABORT_FAIL;
	}



	progress.filterProgress=100;

	if(pFiltered.size())
	{
		IonStreamData *outData = new IonStreamData(this);
		//make a guess as to desired size/colour
		outData->estimateIonParameters(dataIn);
		//override colour to grey
		outData->g = outData->b = outData->r = 0.5;
		outData->valueType = TRANS("Relative Conc. (%)");
		outData->data.swap(pFiltered);
		cacheAsNeeded(outData);

		getOut.push_back(outData);
	}

	//Propagate input streams as desired, propagate everything but the 
	// ion inputs
	size_t mask=STREAM_TYPE_IONS;
	if(!WANT_RANGE_PROPAGATION[algorithm])
		mask|=STREAM_TYPE_RANGE;
	propagateStreams(dataIn,getOut,mask,true);

	return 0;
}


#ifdef DEBUG

bool densityPairTest();
bool nnHistogramTest();
bool rdfPlotTest();
bool axialDistTest();
bool replaceIntersectAndUnionTest();
bool localConcTestRadius();
bool localConcTestNN();
bool replaceSubtractTest();
bool replaceUnionTest();
bool localConcFilterTestNN();
bool localDensityFilterTest();

bool SpatialAnalysisFilter::runUnitTests()
{
	if(!densityPairTest())
		return false;

	if(!nnHistogramTest())
		return false;

	if(!rdfPlotTest())
		return false;

	if(!axialDistTest())
		return false;
	if(!replaceIntersectAndUnionTest())
		return false;

	if(!replaceSubtractTest())
		return false;

	if(!replaceUnionTest())
		return false;

	if(!localConcTestRadius())
		return false;

	if(!localConcTestNN())
		return false;
	
	if(!localConcFilterTestNN())
		return false;


	if(!localDensityFilterTest())
		return false;

	return true;
}


bool densityPairTest()
{
	//Build some points to pass to the filter
	vector<const FilterStreamData*> streamIn,streamOut;

	

	IonStreamData*d = new IonStreamData;
	IonHit h;
	h.setMassToCharge(1);

	//create two points, 1 unit apart	
	h.setPos(Point3D(0,0,0));
	d->data.push_back(h);

	h.setPos(Point3D(0,0,1));
	d->data.push_back(h);

	streamIn.push_back(d);
	//---------
	
	//Create a spatial analysis filter
	SpatialAnalysisFilter *f=new SpatialAnalysisFilter;
	f->setCaching(false);	
	//Set it to do an NN terminated density computation
	bool needUp;
	string s;
	s=TRANS(STOP_MODES[STOP_MODE_NEIGHBOUR]);
	TEST(f->setProperty(KEY_STOPMODE,s,needUp),"Set stop mode");
	s=TRANS(SPATIAL_ALGORITHMS[ALGORITHM_DENSITY]);
	TEST(f->setProperty(KEY_ALGORITHM,s,needUp),"Set algorithm");

	stream_cast(s,1.1/ (4.0/3.0*M_PI));

	TEST(f->setProperty(KEY_CUTOFF,s,needUp),"Set cutoff");


	//Do the refresh
	ProgressData p;
	TEST(!f->refresh(streamIn,streamOut,p),"refresh OK");
	delete f;
	//Kill the input ion stream
	delete d; 
	streamIn.clear();

	TEST(streamOut.size() == 1,"stream count");
	TEST(streamOut[0]->getStreamType() == STREAM_TYPE_IONS,"stream type");

	const IonStreamData* dOut = (const IonStreamData*)streamOut[0];

	TEST(dOut->data.size() == 2, "ion count");

	for(unsigned int ui=0;ui<2;ui++)
	{
		TEST( fabs( dOut->data[0].getMassToCharge()  - 1.0/(4.0/3.0*M_PI))
			< sqrtf(std::numeric_limits<float>::epsilon()),"NN density test");
	}	


	delete streamOut[0];

	return true;
}

bool nnHistogramTest()
{
	//Build some points to pass to the filter
	vector<const FilterStreamData*> streamIn,streamOut;

	

	IonStreamData*d = new IonStreamData;
	IonHit h;
	h.setMassToCharge(1);

	//create two points, 1 unit apart	
	h.setPos(Point3D(0,0,0));
	d->data.push_back(h);

	h.setPos(Point3D(0,0,1));
	d->data.push_back(h);

	streamIn.push_back(d);
	
	//Create a spatial analysis filter
	SpatialAnalysisFilter *f=new SpatialAnalysisFilter;
	f->setCaching(false);	
	//Set it to do an NN terminated density computation
	bool needUp;
	TEST(f->setProperty(KEY_STOPMODE,
		STOP_MODES[STOP_MODE_NEIGHBOUR],needUp),"set stop mode");
	TEST(f->setProperty(KEY_ALGORITHM,
			SPATIAL_ALGORITHMS[ALGORITHM_RDF],needUp),"set Algorithm");
	TEST(f->setProperty(KEY_NNMAX,"1",needUp),"Set NNmax");
	
	//Do the refresh
	ProgressData p;
	TEST(!f->refresh(streamIn,streamOut,p),"refresh OK");
	delete f;

	streamIn.clear();

	TEST(streamOut.size() == 2,"stream count");
	delete streamOut[1]; //wont use this
	
	TEST(streamOut[0]->getStreamType() == STREAM_TYPE_PLOT,"plot outputting");
	const PlotStreamData* dPlot=(const PlotStreamData *)streamOut[0];


	float fMax=0;
	for(size_t ui=0;ui<dPlot->xyData.size();ui++)
	{
		fMax=std::max(fMax,dPlot->xyData[ui].second);
	}

	TEST(fMax > 0 , "plot has nonzero contents");
	//Kill the input ion stream
	delete d; 

	delete dPlot;

	return true;
}

bool rdfPlotTest()
{
	//Build some points to pass to the filter
	vector<const FilterStreamData*> streamIn,streamOut;

	IonStreamData*d = new IonStreamData;
	IonHit h;
	h.setMassToCharge(1);

	//create two points, 1 unit apart	
	h.setPos(Point3D(0,0,0));
	d->data.push_back(h);

	h.setPos(Point3D(0,0,1));
	d->data.push_back(h);

	streamIn.push_back(d);
	
	//Create a spatial analysis filter
	SpatialAnalysisFilter *f=new SpatialAnalysisFilter;
	f->setCaching(false);	
	//Set it to do an NN terminated density computation
	bool needUp;
	TEST(f->setProperty(KEY_STOPMODE,
		TRANS(STOP_MODES[STOP_MODE_RADIUS]),needUp),"set stop mode");
	TEST(f->setProperty(KEY_ALGORITHM,
			TRANS(SPATIAL_ALGORITHMS[ALGORITHM_RDF]),needUp),"set Algorithm");
	TEST(f->setProperty(KEY_DISTMAX,"2",needUp),"Set NNmax");
	
	//Do the refresh
	ProgressData p;
	TEST(!f->refresh(streamIn,streamOut,p),"refresh OK");
	delete f;


	streamIn.clear();

	TEST(streamOut.size() == 1,"stream count");
	TEST(streamOut[0]->getStreamType() == STREAM_TYPE_PLOT,"plot outputting");
	const PlotStreamData* dPlot=(const PlotStreamData *)streamOut[0];


	float fMax=0;
	for(size_t ui=0;ui<dPlot->xyData.size();ui++)
	{
		fMax=std::max(fMax,dPlot->xyData[ui].second);
	}

	TEST(fMax > 0 , "plot has nonzero contents");


	//kill output data
	delete dPlot;

	//Kill the input ion stream
	delete d; 

	return true;
}

bool axialDistTest()
{
	//Build some points to pass to the filter
	vector<const FilterStreamData*> streamIn,streamOut;


	//Create some input data
	//--
	IonStreamData*d = new IonStreamData;
	IonHit h;
	h.setMassToCharge(1);

	//create two points along axis
	h.setPos(Point3D(0,0,0));
	d->data.push_back(h);

	h.setPos(Point3D(0.5,0.5,0.5));
	d->data.push_back(h);

	streamIn.push_back(d);
	//--
	
	
	//Create a spatial analysis filter
	SpatialAnalysisFilter *f=new SpatialAnalysisFilter;
	f->setCaching(false);	

	//Set it to do an axial-dist calculation, 
	// - NN mode termination,
	// - Axial mode
	// - /0 origin, /1 direction, r=1
	// 
	//---
	bool needUp;
	string s;
	s=TRANS(SPATIAL_ALGORITHMS[ALGORITHM_AXIAL_DF]);
	TEST(f->setProperty(KEY_ALGORITHM,s,needUp),"Set prop (algorithm)");
	
	s=TRANS(STOP_MODES[STOP_MODE_NEIGHBOUR]);
	TEST(f->setProperty(KEY_STOPMODE,s,needUp),"Set prop (stopmode)");
	
	Point3D originPt(0,0,0), axisPt(1.1,1.1,1.1);
	float radiusCyl;
	radiusCyl = 1.0f;


	stream_cast(s,originPt);
	TEST(f->setProperty(KEY_ORIGIN,s,needUp),"Set prop (origin)");
	
	stream_cast(s,axisPt);
	TEST(f->setProperty(KEY_NORMAL,s,needUp),"Set prop (axis)");

	stream_cast(s,radiusCyl);
	TEST(f->setProperty(KEY_RADIUS,s,needUp),"Set prop (radius)");
	
	TEST(f->setProperty(KEY_REMOVAL,"0",needUp),"Set prop (disable surface removal)");
	//Do the refresh
	ProgressData p;
	TEST(!f->refresh(streamIn,streamOut,p),"Checking refresh code");
	delete f;
	//Kill the input ion stream
	delete d; 
	streamIn.clear();
	
	//1 plot
	TEST(streamOut.size() == 1,"stream count");
	TEST(streamOut[0]->getStreamType() == STREAM_TYPE_PLOT,"stream type");
	delete streamOut[0];


	return true;
}

bool replaceIntersectAndUnionTest()
{
	std::string ionFile=createTmpFilename(NULL,".pos");
		
	vector<IonHit> ions;
	const unsigned int NIONS=10;
	for(unsigned int ui=0;ui<NIONS;ui++)
		ions.emplace_back(IonHit(Point3D(ui,ui,ui),1));

	IonHit::makePos(ions,ionFile.c_str());
	
	for(unsigned int ui=0;ui<NIONS;ui++)
		ions[ui].setMassToCharge(2);

	IonStreamData *d = new IonStreamData;
	d->data.swap(ions);

	//Create a spatial analysis filter
	SpatialAnalysisFilter *f=new SpatialAnalysisFilter;
	f->setCaching(false);	
	//Set it to do a union calculation 
	bool needUp;
	string s;
	s=TRANS(SPATIAL_ALGORITHMS[ALGORITHM_REPLACE]);
	TEST(f->setProperty(KEY_ALGORITHM,s,needUp),"Set prop");
	TEST(f->setProperty(KEY_REPLACE_FILE,ionFile,needUp),"Set prop");
	s="1";
	TEST(f->setProperty(KEY_REPLACE_VALUE,s,needUp),"Set prop");

	vector<unsigned int> opVec;
	opVec.push_back(REPLACE_MODE_INTERSECT);
	opVec.push_back(REPLACE_MODE_UNION);
	
	ProgressData p;
	vector<const FilterStreamData*> streamIn,streamOut;
	streamIn.push_back(d);
	for(unsigned int opId=0;opId<opVec.size();opId++)
	{
		s=TRANS(REPLACE_ALGORITHMS[opVec[opId]]);
		TEST(f->setProperty(KEY_REPLACE_ALGORITHM,s,needUp),"Set prop");

		//Do the refresh
		TEST(!f->refresh(streamIn,streamOut,p),"refresh OK");

		TEST(streamOut.size() == 1,"stream count");
		TEST(streamOut[0]->getStreamType() == STREAM_TYPE_IONS,"stream type");
		TEST(streamOut[0]->getNumBasicObjects() == NIONS,"Number objects");

		//we should have taken the mass-to-charge from the file
		const IonStreamData *outIons = (const IonStreamData*)streamOut[0];
		for(unsigned int ui=0;ui<NIONS; ui++)
		{
			ASSERT(outIons->data[ui].getMassToCharge() == 1); 
		}
		delete streamOut[0];
		streamOut.clear();
	}
	delete f;
	delete d;
	
	wxRemoveFile(ionFile);

	
	return true;
}

bool replaceSubtractTest()
{
	std::string ionFile=createTmpFilename(NULL,".pos");
		
	vector<IonHit> ions;
	const unsigned int NIONS=10;
	const unsigned int DIFF_COUNT=5;	
	for(unsigned int ui=0;ui<NIONS;ui++)
	{
		IonHit h;
		h = IonHit(Point3D(ui,ui,ui),1);

		//make some ions different to the (x,x,x) pattern
		if(ui < DIFF_COUNT)
		{
			h.setPos(h.getPos() - Point3D(0,0,100));
			ions.push_back(h);
		}
		else
			ions.push_back(h);
	}

	vector<IonHit> tmpI;
	for(unsigned int ui=0;ui<NIONS;ui++)
	{
		if(ions[ui][2] < 0 )
		{
			tmpI.push_back(ions[ui]);
			tmpI.back().setMassToCharge(2);
		}
	}

	IonHit::makePos(tmpI,ionFile.c_str());
	tmpI.clear();

	IonStreamData *d = new IonStreamData;
	d->data.swap(ions);

	//Create a spatial analysis filter
	SpatialAnalysisFilter *f=new SpatialAnalysisFilter;
	f->setCaching(false);	
	
	//Set it to do a subtraction calculation 
	bool needUp;
	string s;
	s=TRANS(SPATIAL_ALGORITHMS[ALGORITHM_REPLACE]);
	TEST(f->setProperty(KEY_ALGORITHM,s,needUp),"Set prop");
	TEST(f->setProperty(KEY_REPLACE_FILE,ionFile,needUp),"Set prop");
	s=TRANS(REPLACE_ALGORITHMS[REPLACE_MODE_SUBTRACT]);
	TEST(f->setProperty(KEY_REPLACE_ALGORITHM,s,needUp),"Set prop");

	//Do the refresh
	ProgressData p;
	vector<const FilterStreamData*> streamIn,streamOut;
	streamIn.push_back(d);
	TEST(!f->refresh(streamIn,streamOut,p),"refresh OK");
	delete f;
	delete d;
	streamIn.clear();

	TEST(streamOut.size() == 1,"stream count");
	TEST(streamOut[0]->getStreamType() == STREAM_TYPE_IONS,"stream type");
	TEST(streamOut[0]->getNumBasicObjects() == DIFF_COUNT,"Number objects");

	//we should have taken the mass-to-charge from the original data,
	// not the file
	const IonStreamData *outIons = (const IonStreamData*)streamOut[0];
	for(unsigned int ui=0;ui<outIons->getNumBasicObjects(); ui++)
	{
		ASSERT(outIons->data[ui].getMassToCharge() == 1); 
		ASSERT(outIons->data[ui].getPos()[2] >= 0); 
	}

	wxRemoveFile(ionFile);

	delete streamOut[0];
	
	return true;
}

bool replaceUnionTest()
{
	std::string ionFile=createTmpFilename(NULL,".pos");
	
	//"B" dataset	
	vector<IonHit> ions;
	ions.emplace_back(IonHit(Point3D(0,0,0),1));
	ions.emplace_back(IonHit(Point3D(1,0,1),1));
	ions.emplace_back(IonHit(Point3D(0,1,1),1));

	IonHit::makePos(ions,ionFile.c_str());
	ions.clear();

	//"A" dataset	
	ions.emplace_back(IonHit(Point3D(0,0,0),2));
	ions.emplace_back(IonHit(Point3D(1,0,-1),2));
	ions.emplace_back(IonHit(Point3D(0,1,-1),2));


	IonStreamData *d = new IonStreamData;
	d->data.swap(ions);

	//Create a spatial analysis filter
	SpatialAnalysisFilter *f=new SpatialAnalysisFilter;
	f->setCaching(false);	
	
	//Set it to do a union calculation 
	bool needUp;
	string s;
	s=TRANS(SPATIAL_ALGORITHMS[ALGORITHM_REPLACE]);
	TEST(f->setProperty(KEY_ALGORITHM,s,needUp),"Set prop");
	TEST(f->setProperty(KEY_REPLACE_FILE,ionFile,needUp),"Set prop");
	s=TRANS(REPLACE_ALGORITHMS[REPLACE_MODE_UNION]);
	TEST(f->setProperty(KEY_REPLACE_ALGORITHM,s,needUp),"Set prop");
	s="1";
	TEST(f->setProperty(KEY_REPLACE_VALUE,s,needUp),"Set prop");

	//Do the refresh
	ProgressData p;
	vector<const FilterStreamData*> streamIn,streamOut;
	streamIn.push_back(d);
	TEST(!f->refresh(streamIn,streamOut,p),"refresh OK");
	delete f;
	delete d;
	streamIn.clear();

	TEST(streamOut.size() == 1,"stream count");
	TEST(streamOut[0]->getStreamType() == STREAM_TYPE_IONS,"stream type");
	TEST(streamOut[0]->getNumBasicObjects() == 5,"Number objects");

	//There should be
	const IonStreamData *outIons = (const IonStreamData*)streamOut[0];
	float sumV=0;
	for(unsigned int ui=0;ui<outIons->getNumBasicObjects(); ui++)
	{
		sumV+=outIons->data[ui].getMassToCharge();
	}
	TEST( EQ_TOL(sumV,7.0f),"mass-to-charge check");

	wxRemoveFile(ionFile);

	delete streamOut[0];
	
	return true;
}

//--- Local chemistry tests --
const IonStreamData *createLCIonStream()
{
	IonStreamData*d = new IonStreamData;
	IonHit h;

	//create some points, of differing mass-to-charge 

	//1 "A" ion, mass 1
	//1 "B" ion, mass 2
	//2 "C" ions,mass 3
	h.setPos(Point3D(0,0,0));
	h.setMassToCharge(1);
	d->data.push_back(h);

	h.setPos(Point3D(0.49,0.0,0.0));
	h.setMassToCharge(2);
	d->data.push_back(h);

	h.setPos(Point3D(0.0,0.5,0.0));
	h.setMassToCharge(3);
	d->data.push_back(h);
	
	h.setPos(Point3D(0.0,0.0,0.51));
	h.setMassToCharge(3);
	d->data.push_back(h);
	
	return d;
}

//Create a single range stream with a range file ~ as follows:
// Ion A: [0.5 1.5]  Ion B: [1.5 2.5]  Ion C: [2.5 3.5]
RangeStreamData *createLCRangeStream()
{
	//Create a fake rangefile
	RangeStreamData *r= new RangeStreamData;
	RangeFile *rng = new RangeFile;

	RGBf colour;
	colour.red=colour.blue=colour.green=0.5;
	unsigned int iid[3] ; 
	iid[0] = rng->addIon("A","A",colour);
	iid[1] = rng->addIon("B","B",colour);
	iid[2] = rng->addIon("C","C",colour);

	rng->addRange(0.5,1.5,iid[0]);
	rng->addRange(1.51,2.5,iid[1]);
	rng->addRange(2.51,3.5,iid[2]);

	r->rangeFile=rng;
	r->enabledRanges.resize(3,1);
	r->enabledIons.resize(3,1);
	return r;
}

//Create a spatial analysis filter that computes
// A (source) B (numerator) C(denominator)
SpatialAnalysisFilter *createLCTestSpatialFilter(const vector<const FilterStreamData *>  &in)
{
	//Create a spatial analysis filter
	SpatialAnalysisFilter *f=new SpatialAnalysisFilter;
	f->setCaching(false);
	//inform it about the rangefile	
	vector< const FilterStreamData *> out;
	f->initFilter(in,out);
	//Set Filter to perform local chemistry analysis 
	// - dist termination,
	//---
	bool needUp;
	string s;
	s=TRANS(SPATIAL_ALGORITHMS[ALGORITHM_LOCAL_CHEMISTRY]);
	if(!(f->setProperty(KEY_ALGORITHM,s,needUp)) )
	{
		cerr << "Failed Set prop (algorithm)";
		return 0;
	}
	
	
	//Set enable/disable status (one for each)
	// A ions - source. B ions - numerator, C ions - denominator
	for(unsigned int ui=0; ui<3; ui++)
	{
		if(ui!=0)
		{
			if(!(f->setProperty(Filter::muxKey(KEYTYPE_ENABLE_SOURCE,ui),"0",needUp)) )
				return 0;
		}
		if(ui!=1)
		{
			if(!(f->setProperty(Filter::muxKey(KEYTYPE_ENABLE_NUMERATOR,ui),"0",needUp)) )
				return 0;
		}
		if(ui!=2)
		{
			if(!(f->setProperty(Filter::muxKey(KEYTYPE_ENABLE_DENOMINATOR,ui),"0",needUp)))
				return 0;
		}
	}
	//---

	return f;
}

bool localConcTestRadius()
{
	//Build some points to pass to the filter
	vector<const FilterStreamData*> streamIn,streamOut;
	
	//Create some input data
	//--
	RangeStreamData *rngStream=createLCRangeStream();
	streamIn.push_back(rngStream);
	streamIn.emplace_back(createLCIonStream());

	//--
	
	SpatialAnalysisFilter *f=createLCTestSpatialFilter(streamIn);
	f->initFilter(streamIn,streamOut);

	bool needUp;	
	string s;
	s=TRANS(STOP_MODES[STOP_MODE_RADIUS]);
	TEST(f->setProperty(KEY_STOPMODE,s,needUp),"Failed Set prop (stop mode)");
	s="1.0";
	TEST(f->setProperty(KEY_DISTMAX,s,needUp),"Failed Set prop (maxDist)");
	
	//Do the refresh
	ProgressData p;
	TEST(!f->refresh(streamIn,streamOut,p),"Checking refresh code");
	delete f;

	//FIXME: Check the data coming out
	TEST(streamOut.size() == 1,"stream size");
	TEST(streamOut[0]->getStreamType() == STREAM_TYPE_IONS,"stream type");
	TEST(streamOut[0]->getNumBasicObjects() == 1,"output ion count");

	IonStreamData *ionD = (IonStreamData *)streamOut[0];

	float localConc = ionD->data[0].getMassToCharge(); 
	TEST(EQ_TOL(localConc,1.0/2.0*100.0),"Local Chemistry check");

	delete rngStream->rangeFile;

	for(unsigned int ui=0;ui<streamIn.size(); ui++)
		delete streamIn[ui];
	streamIn.clear();

	//kill the output ion stream
	for(unsigned int ui=0;ui<streamOut.size(); ui++)
		delete streamOut[ui];
	streamOut.clear();

	return true;
}

bool localConcTestNN()
{
	//Build some points to pass to the filter
	vector<const FilterStreamData*> streamIn,streamOut;
	
	//Create some input data
	//--

	RangeStreamData *rngStream=createLCRangeStream();
	streamIn.push_back(rngStream);
	streamIn.emplace_back(createLCIonStream());

	//--
	
	SpatialAnalysisFilter *f=createLCTestSpatialFilter(streamIn);
	f->initFilter(streamIn,streamOut);
	
	bool needUp;	
	string s;
	s=TRANS(STOP_MODES[STOP_MODE_NEIGHBOUR]);
	TEST(f->setProperty(KEY_STOPMODE,s,needUp),"Failed Set prop (stop mode)");
	s="3";
	TEST(f->setProperty(KEY_NNMAX,s,needUp),"Failed Set prop (nnMax)");
	
	//Do the refresh
	ProgressData p;
	TEST(!f->refresh(streamIn,streamOut,p),"Checking refresh code");
	delete f;

	//FIXME: Check the data coming out
	TEST(streamOut.size() == 1,"stream size");
	TEST(streamOut[0]->getStreamType() == STREAM_TYPE_IONS,"stream type");
	TEST(streamOut[0]->getNumBasicObjects() == 1,"output ion count");

	IonStreamData *ionD = (IonStreamData *)streamOut[0];

	float localConc = ionD->data[0].getMassToCharge(); 
	TEST(EQ_TOL(localConc,1.0/2.0*100.0),"Local Chemistry check");


	delete rngStream->rangeFile;
	for(unsigned int ui=0;ui<streamIn.size(); ui++)
		delete streamIn[ui];
	streamIn.clear();

	//kill the output ion stream
	for(unsigned int ui=0;ui<streamOut.size(); ui++)
		delete streamOut[ui];
	streamOut.clear();

	return true;
}

bool localConcFilterTestNN()
{
	//Build some points to pass to the filter
	vector<const FilterStreamData*> streamIn,streamOut;
	
	//Create some input data
	//--

	RangeStreamData *rngStream=createLCRangeStream();
	streamIn.push_back(rngStream);
	streamIn.emplace_back(createLCIonStream());

	//--
	
	SpatialAnalysisFilter *f=createLCTestSpatialFilter(streamIn);
	
	bool needUp;
	
	TEST(f->setProperty(KEY_ALGORITHM,
				   TRANS(SPATIAL_ALGORITHMS[ALGORITHM_LOCAL_CHEMISTRY_FILTER]),
				   needUp),
		 "Failed to set local chem filter as algorithm");
	
	
	f->initFilter(streamIn,streamOut);
	streamOut.clear();

	string s;
	s=TRANS(STOP_MODES[STOP_MODE_NEIGHBOUR]);
	TEST(f->setProperty(KEY_STOPMODE,s,needUp),"Failed Set prop (stop mode)");
	s="3";
	TEST(f->setProperty(KEY_NNMAX,s,needUp),"Failed Set prop (nnMax)");
	
	//Do the refresh
	ProgressData p;
	TEST(!f->refresh(streamIn,streamOut,p),"Checking refresh code");
	delete f;

	//Check the data coming out
	// Should return an ion steam and 1 range stream
	TEST(streamOut.size() == 2,"stream size");
	unsigned int rangeIdx = (unsigned int) -1;
	unsigned int ionIdx	  = (unsigned int) -1;
	for (unsigned int ui=0; ui<streamOut.size();ui++)
	{
		if (streamOut[ui]->getStreamType() == STREAM_TYPE_IONS)
		{
			ionIdx = ui;
		}
		if (streamOut[ui]->getStreamType() == STREAM_TYPE_RANGE)
		{
			rangeIdx = ui;
		}
	}
	TEST(ionIdx != (unsigned int) -1,"ion stream type");
	TEST(rangeIdx != (unsigned int) -1,"range stream type");
	
	const IonStreamData* outIons = (const IonStreamData*)streamOut[ionIdx];
	
	TEST(outIons->getNumBasicObjects() == 1,"output ion count");

	float localConc = outIons->data[0].getMassToCharge(); 
	TEST(EQ_TOL(localConc,1),"Local Chemistry Filter check");

	// Clean up range stream created initially
	delete rngStream->rangeFile;
	for(unsigned int ui=0;ui<streamIn.size(); ui++)
		delete streamIn[ui];
	streamIn.clear();

	//kill the output ion stream
	delete streamOut[ionIdx];

	return true;
}

bool localDensityFilterTest()
{
	vector<IonHit> ionData;

	ionData.emplace_back(IonHit(Point3D(0,0,0),1));
	ionData.emplace_back(IonHit(Point3D(0,0.5,0.5),1));

	ionData.emplace_back(IonHit(Point3D(100,0,0),1));

	SpatialAnalysisFilter *s = new SpatialAnalysisFilter;

	bool needUpdate;
	string str;
	str=TRANS(SPATIAL_ALGORITHMS[ALGORITHM_DENSITY_FILTER]);
	TEST(s->setProperty(KEY_ALGORITHM, str,needUpdate),"Set algorithm");
	str=TRANS(STOP_MODES[STOP_MODE_RADIUS]);
	TEST(s->setProperty(KEY_STOPMODE,str,needUpdate),"Stop mode");
	TEST(s->setProperty(KEY_DISTMAX,"1",needUpdate),"Search stop radius");

	float cutoff = 4.0/3.0*M_PI + 0.01;
	cutoff = 1.0f/cutoff;
	stream_cast(str,cutoff);
	s->setProperty(KEY_CUTOFF,str,needUpdate);

	
	IonStreamData *isd = new IonStreamData;
	isd->data = ionData;

	vector<const FilterStreamData* > inData,outData;
	inData.push_back(isd);
	
	ProgressData p;
	TEST(!s->refresh(inData,outData,p),"Checking refresh code");
	
	TEST(outData.size() ==1,"Output size");

	TEST(outData[0]->getStreamType() == STREAM_TYPE_IONS,"Stream type");

	const IonStreamData *outS;
	outS = (const IonStreamData *)outData[0];

	TEST(outS->data.size() ==2,"stream test");
	delete s;
	delete isd;

	return true;
}

//--------------------------------

#endif

