import ij.*;
import ij.plugin.*;
import ij.plugin.filter.*;
import ij.plugin.Animator.*;
import ij.process.*;
import ij.gui.*;
import ij.measure.*;
import ij.text.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import ij.plugin.*;

/** 	Besides area, perimeter and angle, this plugin calculates shape descriptors. The mean and standard deviation of shape values
		are given for every image in a stack. The definitions are given according to Russ, 1999 (The image processing Handbook).
		The following descriptors are given:
			Form factor: 4pi*area/sqr(perimeter)
			Roundness: 4*area/pi*sqr(major axis)
			Compactness: sqrt((4/pi)*area)/major axis
			Aspect Ratio: major axis/minor axis
			Effective diameter= sqrt(area/pi)*2
			Solidity = area/convex area
			Convexity = convex perimeter/perimeter

	Gary Chinga Carrasco 020709

	Modification
	020829	Added option to display single particle shape descriptors
	040214	Added the solidity and convexity descriptors (as given by the convex hull algorithm implemented by Wayne Rasband in IJ 1.31g)
			The plugin requires binary images.
	040322	Added new option to display a polar/rose plot based on the particle orientations (v. 1b)
			Added option to include solidity and convexity values.
			Changes to the dialog box

	040605	Perform particle analysis on particles having an area>min. size and an aspect ratio>min.AR
			Added the effective diameter descriptor.	(v.1c)
	040607	Added option to display particle outline. Requires IJ 1.33d	(v. 1c)
	040623	Added several options to consider the particles having a specific form factor, roundness, compactness and aspect ratio (v. 1d)
 	040706	Expanded the shape options to include min and max values (v. 1e)
			Form factor: 4pi*area/sqr(perimeter)
			Roundness: 4*area/pi*sqr(major axis)
			Compactness: sqrt((4/pi)*area)/major axis
			Aspect Ratio: major axis/minor axis
			Effective diameter= sqrt(area/pi)*2
			Solidity = area/convex area
			Convexity = convex perimeter/perimeter
			
 	050516	Some modifications to the dialog box.
 			Particles not having the specified shape options are removed from the original image.
 			The outline images shows the particle number corresponding to the single result table.
 	051017	Fix a small NullPointerException bug (1h)
 	051107	Included a "Include edge particle" option.
 	051209	Included the major and minor axis in the result table.
		Changes to the outline window display (v.1j)
	060225	Include an option to include holes. Particle with holes may understimate shape values based on area measuremens, like form factor and Solidity
	070405	Fixed a bug in the classifyOrientations method. The plot weight the area in pixels, not calibrated units. (v. 1p)
	070405	Particle classification according to angle limits is introduced. (v. 1r)
	090721	Included the Feret diameters. 
		Changes made to the dialog box. Outlines are displayed by default(v. 1t)
	090722	Verif’ed if the image uses an inverted lut.(v.1u)
 */

public class Shape_Descriptor1u implements PlugIn, Measurements,ItemListener {

	/** Display results in the ImageJ console. */
	public static final int SHOW_RESULTS = 1, SHOW_OUTLINES = 4, EXCLUDE_EDGE_PARTICLES =8, SHOW_SIZE_DISTRIBUTION = 16;// FLOOD_FILL=1024;
	public static final int SHOW_PROGRESS = 32, CLEAR_WORKSHEET = 64, RECORD_STARTS = 128,DISPLAY_SUMMARY = 256, SHOW_NONE = 512, SHOW_MASKS=4096;
	protected static final int NOTHING=0,OUTLINES=1,MASKS=2,ELLIPSES=3,INCLUDE_HOLES=1024;
	static final int PARCONVEXITY=13, PAR=16;
	int inter=2;
	double fFactor=1, aRatio=1, roundness=1, compactness=1;
	float[] areas,per, angle, mAxis, miAxis;
	int pd=400; //plot window dimension

	ImagePlus imp;
 	TextWindow mtw;
	String title,headings,aLine ;
	double minSize, maxSize;
	int dec;
	boolean canceled=false, pPlot, weighOrientation=true, iConvex=false, sOutlines=false,iEdge=true, clear=false, iHoles=true;
	boolean changeValue=false;

	double[] pLim,arLim,ffLim,cLim,rLim, aLim;
	String s;
	private static String[] items = {"Single particle details","Mean particle details"};
	protected static final int SINGLE=0,MEAN=1;
	protected static int Choice;

	Checkbox cbpPlot = new Checkbox("Display orientation plot",pPlot);
	Checkbox cbiHoles = new Checkbox("Include holes",iHoles);
	Checkbox cbClear = new Checkbox("Clear particles",clear);
	Checkbox cbWeighArea = new Checkbox("Weigh orientation",weighOrientation);
	Checkbox cbIEdgePart = new Checkbox("Include edge particles",iEdge);
	TextField tfInterval = new TextField("10",0);
	Label lblInterval = new Label("Interval width (0-40 degrees): ");
	Label lblMinMax = new Label("Particle size: ");
	TextField tfMinMax = new TextField("0,99999999",0);
	Label lblAR = new Label("Aspect ratio (>1): ");
	TextField tfAR = new TextField("1.0,50",0);
	Label lblFF = new Label("Form factor (0-1): ");
	TextField tfFF = new TextField("0.0,1.0",0);
	Label lblC = new Label("Compactness (0-1): ");
	TextField tfC = new TextField("0.0,1.0",0);
	Label lblR = new Label("Roundness (0-1): ");
	TextField tfR = new TextField("0.0,1.0",0);
	Label lblA = new Label("Angle (0-180): ");
	TextField tfA = new TextField("0.0,180",0);
	Label lblCx = new Label("");
	
	Label lblNone = new Label("");
	Label lblNone1 = new Label("");
	Label lblNone2 = new Label("");
	Label lblNone3 = new Label("");
	Label lblFilter = new Label("Consider particles having:");
	Label lblNone5 = new Label("");
	Label lblNone6 = new Label("");

	ResultsTable rt = new ResultsTable();

	public void run(String arg) {
		if (IJ.versionLessThan("1.43d"))
			return;
		imp  = IJ.getImage();
		if (imp.getBitDepth()!=8)
			IJ.error("8-bit image required");
		
		if (imp.isInvertedLut()){
			IJ.run("Invert");
			IJ.run("Invert LUT");
		}
		
		ImageProcessor ip = imp.getProcessor();
		ImageStack stack = imp.getStack();
		//String 
		
		int nSlices = imp.getStackSize();
		int w=stack.getWidth();
		int h=stack.getHeight();
		ImageStack ellipseStack = new ImageStack(pd,pd);
		ImageStack outlines = new ImageStack(w,h);
		
		ImageProcessor ipTemp=imp.getProcessor();
		
		//String iName= imp.getShortTitle();
		String iName= imp.getTitle();
		
		IJ.run("Colors...", "foreground=white background=white selection=yellow");
		Calibration cal = imp.getCalibration();
		float ps = (float) cal.pixelWidth;

		getDetails(ps);
		if (canceled) return;
		int classes=(int)(180/inter); //inter = 360/tClasses
		int tClasses=2*classes;

		int measurements = Analyzer.getMeasurements(); // defined in Set Measurements dialog
		Analyzer.setMeasurements(0);
		measurements |= AREA+PERIMETER+ELLIPSE+SHAPE_DESCRIPTORS+FERET; //make sure area and perimeter are measured
		int options=0;
		Analyzer.setMeasurements(measurements);
		ParticleAnalyzer pa = new ParticleAnalyzer(options, measurements, rt, 0, 99999999);
		int nShapes=PAR;
		
		String sLabel="";
		ImagePlus impTemp = new ImagePlus("TEMP",ipTemp);
		ByteProcessor ipTempSlice;
		
		for (int i=1; i<=nSlices; i++) {
			imp.setSlice(i);
			IJ.showStatus("a: "+i+"/"+nSlices+", Processing...");
			ImageProcessor ipSlice = stack.getProcessor(i);
			sLabel=stack.getSliceLabel(i);
			
			String imageLabel= stack.getSliceLabel(i);
			byte[] temp = (byte[])ipSlice.getPixelsCopy();
			ipTempSlice = new ByteProcessor(w,h,temp, null);
			impTemp= new ImagePlus("TEMP", ipTempSlice);
			//impTemp.show();
			
			if (iEdge) options =  CLEAR_WORKSHEET+RECORD_STARTS+SHOW_NONE;
			else options =  EXCLUDE_EDGE_PARTICLES+CLEAR_WORKSHEET+RECORD_STARTS+SHOW_NONE;
			if (iHoles) options += INCLUDE_HOLES;
			pa = new ParticleAnalyzer(options, measurements, rt, 0, 99999999);
			
			if (!pa.analyze(imp))
				return;

			float[][] shapes;// = new float[nShapes][counter];
			float[][] meanShapes = new float[nShapes][2];	//two colums for mean and std

			if (rt.getCounter()>0){
				if (changeValue&&!clear)
					filterImage(rt,impTemp,pLim,ffLim, rLim, arLim, cLim,aLim);
				if (clear)
					filterImage(rt,imp,pLim,ffLim, rLim, arLim, cLim,aLim);
			}
			
			rt.reset();
			
			if (iEdge) options =  CLEAR_WORKSHEET+RECORD_STARTS+SHOW_OUTLINES;
			else options =  EXCLUDE_EDGE_PARTICLES+CLEAR_WORKSHEET+RECORD_STARTS+SHOW_OUTLINES;
			if (iHoles) options += INCLUDE_HOLES;
			pa = new ParticleAnalyzer(options, measurements, rt, 0, 99999999);
				
			if (clear){
				if (!pa.analyze(imp))
					return;
			}
			
			if (changeValue&&!clear){
				if (!pa.analyze(impTemp))
					return;
			}
			
			ImagePlus impOutline=IJ.getImage();
			ImageProcessor ipOutline=impOutline.getProcessor();
			outlines.addSlice(sLabel,ipOutline);
			if(!clear){ IJ.selectWindow("Drawing of TEMP");IJ.run("Close");}
			else{ IJ.selectWindow("Drawing of "+iName);IJ.run("Close");}
		
			int counter = rt.getCounter();
			if (counter>0) {
				shapes = calculateShape(rt,pLim,ffLim, rLim, arLim, cLim, aLim);
				if (Choice==SINGLE){
					for (int ii=0; ii<shapes[1].length; ii++){
						IJ.showProgress(ii, shapes[1].length-1);
						writeSingleResults(shapes,i,ii,nShapes, imageLabel);
					}
				}
				else{
					for (int n=0;n<nShapes;n++){
						float[] tArray = new float[shapes[1].length]; //the single measurements are passed to tArray.
						for (int ii=0; ii<shapes[1].length; ii++)
							tArray[ii]=shapes[n][ii];
						meanShapes[n][0]=calculateMean(tArray);
						meanShapes[n][1]=calculateStd(meanShapes[n][0],tArray);
					}
					writeMeanResults(meanShapes,i,shapes[1].length,imageLabel);
				}
			}
			if ((pPlot)&&(rt.getCounter()>0)){
				int[] aAngles = classifyOrientations(rt, tClasses, weighOrientation, ps);
				int[][] xyCoords=calCulatePolarPlot(aAngles, classes);	//returns the x,y coordinates in a polar plot
				ByteProcessor bpTemp=makePolarImage(xyCoords,tClasses);
				ellipseStack.addSlice("Test",bpTemp);
			}
			rt.reset();
			impTemp.close();
			
		}
		if (pPlot){
			ImagePlus impPlot = new ImagePlus("Orientation, Intervall="+inter+" degs",ellipseStack);
			impPlot.updateAndDraw();
			impPlot.show();IJ.run("Fire");
		}

		ImagePlus impOutlines = new ImagePlus("Outlines of "+iName,outlines);
		impOutlines.updateAndDraw();
		impOutlines.show();//IJ.run("Fire");
	}

	private float[] getPixelValues(ImageProcessor ipTemp){
		int ww=ipTemp.getWidth();
		int hh=ipTemp.getHeight();
		float[] pValues = new float[ww*hh];
		for (int y = 0; y < hh; y++){
			for (int x = 0; x < ww; x++){
				int index = x + ww*y;
				pValues[index]=ipTemp.getPixelValue(x,y);
			}
		}
		return pValues;
	}
	
	void filterImage(ResultsTable rt, ImagePlus impTemp,double[] areaLim, double[] fFact, double[] rou, double[] ar, double[] comp, double [] ang){
		int nParticles = rt.getCounter();

		float[] a = rt.getColumn(ResultsTable.AREA); // get area measurements
		float[] p = rt.getColumn(ResultsTable.PERIMETER); // get perimeter measurements
		float[] an= rt.getColumn(ResultsTable.ANGLE); // get angle measurements
		float[] mA= rt.getColumn(ResultsTable.MAJOR);
		float[] miA= rt.getColumn(ResultsTable.MINOR);

		//ImagePlus impTemp = new ImagePlus("Test", ipTemp);
		WindowManager.setTempCurrentImage(impTemp);
		impTemp.unlock();

		float[][] sDescriptors = new float[PAR][a.length];
		int c=0;
		for (int ii=0; ii<a.length; ii++){
			IJ.showProgress(ii, a.length-1);
			float AR = (float)(mA[ii]/miA[ii]);
			float FF = (float)((4*Math.PI*a[ii])/(sqr(p[ii])));if (FF>1) FF=1;
			float R = (float)((4*a[ii])/(Math.PI*sqr(mA[ii])));if (R>1) R=1;
			float C = (float)(Math.sqrt((4/Math.PI)*a[ii])/mA[ii]);if (C>1) C=1;

			//IJ.showMessage(IJ.d2s(a[ii])+"  "+IJ.d2s(areaLim[0])+"  "+IJ.d2s(areaLim[1]));
			if (a[ii]<areaLim[0]||a[ii]>areaLim[1]||FF<fFact[0]||FF>fFact[1]||R<rou[0]||R>rou[1]||C<comp[0]||C>comp[1]||AR<ar[0]||AR>ar[1]||an[ii]<ang[0]||an[ii]>ang[1]){
				IJ.doWand((int)rt.getValue("XStart", ii),(int)rt.getValue("YStart", ii));
				IJ.run("Clear", "slice");
			}
		}
		IJ.run("Select None");
	}

	int[] classifyOrientations(ResultsTable rt, int tc, boolean weigh, float p){
		float[] a = rt.getColumn(ResultsTable.AREA); // get area measurements
		float[] an= rt.getColumn(ResultsTable.ANGLE); // get angle measurements
		float[] lA= rt.getColumn(ResultsTable.MAJOR);
		float[] sA= rt.getColumn(ResultsTable.MINOR);

		int c = tc/2;
		int in=(int)(360/tc);
		int[] oArray = new int[c];
		for (int i=0; i<an.length;i++){
			IJ.showStatus("Generating polar plot...");
			IJ.showProgress(i, an.length-1);
			for (int ii=0; ii<c; ii++){
				int lLim=ii*in;				//find lower and upper limit within each interval
				int uLim=(ii+1)*in;
				if ((an[i]>=lLim)&&(an[i]<uLim)){
					if (weigh) oArray[ii]+=a[i]/p;  //weigh in pixels to avoid calibration problems             
					else oArray[ii]++;
				}
			}
		}
		return oArray;
	}

	ByteProcessor makePolarImage(int[][] xy, int tc){
		boolean plotCoords=true;
		int ww=pd, hh=pd;
		ByteProcessor ipTemp = new ByteProcessor(ww,hh);
		ipTemp.setColor(Color.white);ipTemp.fill();
		ipTemp.setColor(new Color(120,120,120));
		ipTemp.setLineWidth(2);
		ipTemp.setFont(new Font("SansSerif",Font.PLAIN,12));
		ipTemp.setAntialiasedText(false);

		ipTemp.moveTo(xy[0][0], xy[1][0]);
		 for (int ii=0; ii<tc; ii++)
			 ipTemp.lineTo(xy[0][ii], xy[1][ii]);

		 ipTemp.lineTo(xy[0][0], xy[1][0]);

       	//Write coordinates and cross in the middle.
       		ipTemp.setColor(new Color(40,40,40));
		ipTemp.setLineWidth(1);
		ipTemp.moveTo(ww/2-5, ww/2);
		ipTemp.lineTo(ww/2+5, ww/2);
		ipTemp.moveTo(ww/2, ww/2-5);
		ipTemp.lineTo(ww/2, ww/2+5);

       	//ipTemp.setColor(new Color(0,0,0));
       		ipTemp.drawString("0", ww-14, ww/2);
		ipTemp.drawString("90", ww/2-6, 13);
		ipTemp.drawString("180", 2, ww/2);
		ipTemp.drawString("270", ww/2, ww);

		ipTemp.drawRect(ww-30, ww/2-14, 30, 14);
		ipTemp.drawRect(ww/2-15, 0, 40, 14);
		ipTemp.drawRect(0, ww/2-14, 30, 14);
		ipTemp.drawRect(ww/2-10, ww-14, 40, 14);

		return ipTemp;
	}

	int[][] calCulatePolarPlot(int[] aAngles,int c){
		int ww=pd,hh=pd;
		int in=(int)(180/c);
		int [][] Coords = new int[2][2*c];
		for (int ii=0; ii<c; ii++){
			double angle = ii*in*Math.PI/180; 					//convert the angles to radians
			Coords[0][ii]= -(int)(aAngles[ii]*Math.cos(angle)); //Inverse the x-axis
			Coords[1][ii]= (int)(aAngles[ii]*Math.sin(angle));
			Coords[0][ii+c]=-Coords[0][ii];						//Duplicates the halv plot
			Coords[1][ii+c]=-Coords[1][ii];						//duplicates the halv plot
		}
		//move the plot to positive values and
		int yMin = 999999,xMin = 999999,yMax = -999999,xMax = -999999;
		for (int ii=0; ii<2*c;ii++){
			if (Coords[0][ii]<xMin) xMin = Coords[0][ii];
			else if (Coords[0][ii]>xMax) xMax = Coords[0][ii];
			if (Coords[1][ii]<yMin) yMin = Coords[1][ii];
			else if (Coords[1][ii]>yMax) yMax = Coords[1][ii];
		}
		//rescale to fit the 400x400 image
		double scaleFactor=1;
		double maxDist;
		if ((xMax-xMin)>(yMax-yMin)) maxDist = (xMax-xMin);
		else maxDist = (yMax-yMin);

		scaleFactor= maxDist/(ww-100);
		int xP = (int)((ww-((xMax-xMin)/scaleFactor))/2); 	//Finds the starting point
		int yP = (int)((ww-((yMax-yMin)/scaleFactor))/2);	//The plot is placed on the center of the image.

		//moves the plot to the center of the image.
		for (int ii=0; ii<2*c;ii++){
			Coords[0][ii] = (int)(xP+(Coords[0][ii]+ Math.abs(xMin))/scaleFactor);
			Coords[1][ii] = (int)(yP+(Coords[1][ii]+ Math.abs(yMin))/scaleFactor);
		}
		return Coords;
	}

	float[][] trimArray(float[][] tArray, int n){
		float[][] newArray = new float[tArray.length][n];
		for (int i=0;i<n;i++)
			for (int ii=0;ii<tArray.length;ii++)
				newArray[ii][i]=tArray[ii][i];
		return newArray;
	}

	float[][] calculateShape(ResultsTable rt,double[] areaLim, double[] fFact, double[] rou, double[] ar, double[] comp, double[] ang){

			float[] a = rt.getColumn(ResultsTable.AREA); // get area measurements
			float[] p = rt.getColumn(ResultsTable.PERIMETER); // get perimeter measurements
			float[] an= rt.getColumn(ResultsTable.ANGLE); // get angle measurements
			float[] mA= rt.getColumn(ResultsTable.MAJOR);
			float[] miA= rt.getColumn(ResultsTable.MINOR);
			float[] f= rt.getColumn(ResultsTable.FERET);
			float[] fa= rt.getColumn(ResultsTable.FERET_ANGLE);			
			float[] minf= rt.getColumn(ResultsTable.MIN_FERET);
			float[] s= rt.getColumn(ResultsTable.SOLIDITY );
			
	
//			IJ.write(IJ.d2s(a.length)+"  "+IJ.d2s(areaLim[1])+"  "+IJ.d2s(fFact[0])+"  "+IJ.d2s(fFact[1]));
			float[][] sDescriptors = new float[PAR][a.length];
			int c=0;
			for (int ii=0; ii<a.length; ii++){
				IJ.showProgress(ii, a.length-1);
				float AR = (float)(mA[ii]/miA[ii]);
				float FF = (float)((4*Math.PI*a[ii])/(sqr(p[ii])));if (FF>1) FF=1;
				float R = (float)((4*a[ii])/(Math.PI*sqr(mA[ii])));if (R>1) R=1;
				float C = (float)(Math.sqrt((4/Math.PI)*a[ii])/mA[ii]);if (C>1) C=1;


				if (a[ii]>=areaLim[0]&& a[ii]<=areaLim[1]){
					if ((FF>=fFact[0])&&(FF<=fFact[1])){
						if ((R>=rou[0])&&(R<=rou[1])){
							if ((C>=comp[0])&&(C<=comp[1])){
								if ((AR>=ar[0])&&(AR<=ar[1])){
									if ((an[ii]>=ang[0])&&(an[ii]<=ang[1])){
										sDescriptors[0][c]=ii+1;
										sDescriptors[1][c]=a[ii];
										sDescriptors[2][c]=p[ii];
										sDescriptors[3][c]=an[ii];
										sDescriptors[4][c]=FF;		//FORM FACTOR
										sDescriptors[5][c]=R; 		//ROUNDNESS
										sDescriptors[6][c]=C; 	//COMPACTNESS
										sDescriptors[7][c]=AR; 						//ASPECT RATIo
										//sDescriptors[8][c]=(float)((p[ii]*p[ii])/a[ii]); 						//Circularity
																//Effective diameter
										sDescriptors[8][c]=mA[ii];
										sDescriptors[9][c]=miA[ii];
										sDescriptors[10][c]=(float)(Math.sqrt(a[ii]/Math.PI)*2); 
										
										sDescriptors[11][c]=f[ii]; 
										sDescriptors[12][c]=minf[ii]; 
										sDescriptors[13][c]=fa[ii]; 
										sDescriptors[14][c]=s[ii];								//Solidity
										sDescriptors[15][c]=s[ii];;	
										c++;
									}
								}
							}
						}
					}
				}

			}
			//for (int i=0;i<c;i++)
			//	IJ.write(IJ.d2s(sDescriptors[0][i]));
			float[][] sShapes = trimArray(sDescriptors, c);
			return sShapes;
		}


	float calculateMean(float[] dataset){
		double mValue=0;
		for (int j=0; j<dataset.length; j++){mValue += dataset[j];}
		return (float) (mValue/dataset.length);
	}

	float calculateStd(float mValue,float[] dataset){
		float sValue=0;
		if (dataset.length==1) {return (float) (sValue);}
		else{
			for (int j=0; j<dataset.length; j++){sValue += sqr(mValue-dataset[j]);}
			return (float) (Math.sqrt(sValue/(dataset.length-1)));
		}
	}

	void writeMeanResults(float[][] sDesc, int slice, int p, String lab){
			if (lab==null) lab=IJ.d2s(slice,0);
			aLine = lab+"\t"+p+"\t";
			for (int ii=1;ii<sDesc.length-1;ii++)
				aLine += IJ.d2s(sDesc[ii][0],dec)+"\t"+IJ.d2s(sDesc[ii][1],dec)+"\t";
			if (mtw==null) {
				title = "Mean values of "+imp.getShortTitle();//+", min size="+IJ.d2s(minSize,0)+", max size="+IJ.d2s(maxSize,0)+", FF="+IJ.d2s(fFactor,2)+", R="+IJ.d2s(roundness,2)+", C="+IJ.d2s(compactness,2)+", AR="+IJ.d2s(aRatio,2);
				//headings = "Slice\t#Particles\tArea\tstd\tPerimeter\tstd\tAngle\tstd\tForm Factor\tstd\tRound\tstd\tComp\tstd\tAR\tstd\tMajor\tstd\tMinor\tstd\tEff. Diam.\tstd\tFeret\tstd\tMin_Feret\tstd\tFeret_angle\tstd\tSolidity\tstd\tConvexity\tstd";
				headings = "Slice\t#Particles\tArea\tstd\tPerimeter\tstd\tAngle\tstd\tForm Factor\tstd\tRound\tstd\tComp\tstd\tAR\tstd\tMajor\tstd\tMinor\tstd\tEff. Diam.\tstd\tFeret\tstd\tMin_Feret\tstd\tFeret_angle\tstd\tSolidity\tstd";
				mtw = new TextWindow(title, headings, aLine, 850, 180);
			}
			else
				mtw.append(aLine);
		}

	void writeSingleResults(float[][] sDesc, int slice,int n, int p, String lab){
			if (lab==null) lab=IJ.d2s(slice,0);
			aLine = lab+"\t"+IJ.d2s(sDesc[0][n],0)+"\t";
			for (int ii=1;ii<p-1;ii++)
				aLine += IJ.d2s(sDesc[ii][n],dec)+"\t";
			if (mtw==null) {
				title = "Single particle values of "+imp.getShortTitle();//+", min size="+IJ.d2s(minSize,0)+", max size="+IJ.d2s(maxSize,0)+", FF="+IJ.d2s(fFactor,2)+", R="+IJ.d2s(roundness,2)+", C="+IJ.d2s(compactness,2)+", AR="+IJ.d2s(aRatio,2);
				//headings = "Slice\tParticle\tArea\tPerimeter\tAngle\tForm Factor\tRound\tComp\tAR\tMajor\tMinor\tEff. Diam.\tFeret\tMin_Feret\tFeret_angle\tSolidity\tConvexity";
				headings = "Slice\tParticle\tArea\tPerimeter\tAngle\tForm Factor\tRound\tComp\tAR\tMajor\tMinor\tEff. Diam.\tFeret\tMin_Feret\tFeret_angle\tSolidity";
				
				mtw = new TextWindow(title, headings, aLine, 550, 180);
			}
			else
				mtw.append(aLine);
	}

	double sqr(double x) {return x*x;}

    public double[] s2doubles(String s) {
        StringTokenizer st = new StringTokenizer(s, ", \t");
        int nDoubles = st.countTokens();
        double[] doubles = new double[nDoubles];
        for(int i=0; i<nDoubles; i++) {
            try {doubles[i] = Double.parseDouble(st.nextToken());}
            catch (NumberFormatException e) {IJ.write(""+e); return null;}
        }

	    Arrays.sort(doubles);
		return doubles;
    }

	void getDetails(float ps) throws NumberFormatException{
		GenericDialog gd = new GenericDialog("Shape descriptors 1t...");
		String units = imp.getCalibration().getUnits();
		gd.setLayout(new GridLayout(14,2,0,0));
		gd.add(lblFilter); gd.add(lblNone5);
		gd.add(lblMinMax); gd.add(tfMinMax);
		gd.add(lblAR); gd.add(tfAR);
		gd.add(lblFF); gd.add(tfFF);
		gd.add(lblC); gd.add(tfC);
		gd.add(lblR); gd.add(tfR);
		gd.add(lblA); gd.add(tfA);
		
		gd.add(cbClear); gd.add(cbiHoles);
		//gd.addCheckbox("Show Outlines", sOutlines);
		//gd.add(lblCx);
		gd.addChoice("Calculate", items, items[Choice]);
		gd.addNumericField(" Decimal places: ",2, 0);
		gd.add(cbpPlot);
		gd.add(cbWeighArea);
		gd.add(lblInterval);
		gd.add(tfInterval);
		gd.add(cbIEdgePart);
		
		cbpPlot.setVisible(true);
		cbWeighArea.setVisible(false);
		lblInterval.setVisible(false);
		tfInterval.setVisible(false);
		gd.add(lblNone2);//gd.add(lblNone3);

		if (!units.startsWith("pixel"))
			gd.addMessage("        (Pixel size = "+IJ.d2s(ps,2)+" "+units+")");
		else gd.add(lblNone1);

		cbpPlot.addItemListener(this);
		cbClear.addItemListener(this);
		
		gd.showDialog();
		if (gd.wasCanceled()){
			canceled = true;
			return;
		}
		Choice = gd.getNextChoiceIndex();
		//sOutlines=gd.getNextBoolean();
		//iConvex=gd.getNextBoolean();
		pPlot = cbpPlot.getState();
		weighOrientation = cbWeighArea.getState();
		iEdge = cbIEdgePart.getState();
		clear = cbClear.getState();
		iHoles = cbiHoles.getState();

		try{
			pLim=s2doubles(tfMinMax.getText());
			arLim=s2doubles(tfAR.getText());
			ffLim=s2doubles(tfFF.getText());
			cLim=s2doubles(tfC.getText());
			rLim=s2doubles(tfR.getText());
			aLim=s2doubles(tfA.getText());
			minSize = pLim[0];
			maxSize = pLim[1];
			
			if (pLim[0]>0||pLim[1]!=99999999) changeValue=true;
			if (arLim[0]>0||arLim[1]!=50) changeValue=true;
			if (ffLim[0]>0||ffLim[1]<1.0) changeValue=true;
			if (cLim[0]>0||cLim[1]<1.0) changeValue=true;
			if (rLim[0]>0||pLim[1]<1.0) changeValue=true;
			if (aLim[0]>0||aLim[1]<180) changeValue=true;
			
			
			dec = (int)gd.getNextNumber();
			inter=(int)(Double.parseDouble(tfInterval.getText()));
			if ((minSize<0)||(minSize>maxSize)){
				IJ.showMessage("Invalid input number");
				canceled=true;
			}
			else{
				if (dec<0){
					IJ.showMessage("Invalid input number");
					canceled=true;
				}
				else{
					if ((inter<0)||(inter>40)){
						IJ.showMessage("Min and max interval are 0 and 40 degress respectively");
						canceled=true;
					}
					else{
						if ((ffLim[1]>1)||(ffLim[0]<0)){			//Increases the FF to 1.5 due to the uncertainty of small particles.
							IJ.showMessage("Invalid input number, 0<=form factor<=1");
							canceled=true;
						}
						else{
							if ((rLim[1]>1)||(rLim[0]<0)){
								IJ.showMessage("Invalid input number, 0<=roundness<=1");
								canceled=true;
							}
							else{
								if (arLim[0]<1){
									IJ.showMessage("Invalid input number, Aspect ratio > 1");
									canceled=true;
								}
								else{
									if ((cLim[1]>1)||(cLim[0]<0)){
										IJ.showMessage("Invalid input number, 0<=Compactness<=1");
										canceled=true;
									}
									else{
										if ((aLim[1]>180)||(aLim[0]<0)){
											IJ.showMessage("Invalid input number, 0<=Angle<=180");
											canceled=true;
										}
									}
								}
							}
						}
					}
				}
			}

		}

		catch(NumberFormatException e){
			IJ.showMessage("Invalid input number");
			canceled = true;
		}
	}

	public void itemStateChanged(ItemEvent e) {

		if(e.getItemSelectable() == cbpPlot){
				if (e.getStateChange() == ItemEvent.SELECTED) {
					cbWeighArea.setVisible(true);
					lblInterval.setVisible(true);
					tfInterval.setVisible(true);
				}
				else {
					cbWeighArea.setVisible(false);
					lblInterval.setVisible(false);
					tfInterval.setVisible(false);
				}
		}  					

	}
}

