001    import ij.IJ;
002    import ij.ImagePlus;
003    import ij.WindowManager;
004    import ij.gui.OvalRoi;
005    import ij.gui.PolygonRoi;
006    import ij.gui.Roi;
007    import ij.measure.Measurements;
008    import ij.measure.ResultsTable;
009    import ij.plugin.filter.Analyzer;
010    import ij.plugin.filter.PlugInFilter;
011    import ij.process.ImageProcessor;
012    import ij.process.ImageStatistics; 
013    import java.awt.Point;
014    import java.awt.Rectangle;
015    import java.awt.geom.Point2D;
016    /**
017     * Plugin for ImageJ.
018     * Finds and draws a convex hull
019     * by Thomas R. Roy, University of Alberta, Canada.
020     * Finds the smallest bounding circle using three points of
021     * the convex hull for a binary image, as proposed by Xavier Draye, and
022     * a modification in the case that this algorithm does not work, using
023     * the largest triangle made from points on the hull.
024     * The image has to be binary.
025     
026     * @since date: 2002-2004
027     * @since updated January 1, 2005
028     * @since updated to check for binary images May 23, 2005
029     * @author A. Karperien
030     * Charles Sturt University, Australia
031     * @author Thomas R. Roy
032     * University of Alberta, Canada
033     * @author Wayne Rasband, NIH
034     */
035    
036    public class HullAndCircle05_ implements PlugInFilter, Measurements {
037        /**variables for circularity*/
038        float majorcircle=0, minorcircle=0, midx=0, midy=0;
039        /**Variable for bounding circle's diameter.*/
040        double diameter=0;
041        private int width,height;  //width and height of window
042        private int imgHeight, imgWidth; //dimensions of pixelated area
043        //private float centrex, centrey;
044        private int left, top, right, bottom;
045        private int topxc, rightyc, leftyc, bottomxc;
046        private String filename;
047        private ImagePlus img;
048        ImageStatistics stats=null;
049        int foreground = 0;
050        int X[] = new int [maxImageWidth * 2];
051        int Y[] = new int [maxImageWidth * 2];
052        
053        private static final int maxImageWidth = 5000;
054        /**
055         * Returns the array of ints based on the doubles.
056         *
057         * @param   A array of doubles
058         * @param   length length of array to convert
059         * @return  int array
060         */
061        public static int[] DoubleArrayToIntArray(double[] A, int length) {
062            int[]N=new int[length];
063            for (int i = 0; i < length; i++)
064                N[i]=(int)(A[i]);
065            return N;
066        }
067        /**
068         * Gets the centre and diameter for
069         *      the circle from the passed three points
070         *      (a,b), (c,d), and (e,f).  Solves three equations
071         *      simultaneously using the basic
072         *      equation for a circle.
073         *      (h-x)<sup>2</sup>+(k-y)<sup>2</sup>=r<sup>2</sup>
074         *      Does not check for colinearity.
075         *      @param a x coordinate of first point
076         *      @param b y coordinate of first point
077         *      @param c x coordinate of second point
078         *      @param d y coordinate of second point
079         *      @param e x coordinate of third point
080         *      @param f y coordinate of third point
081         *      @return double [] containing
082         *      the centre coordinates(h, k) and the diameter
083         *      of the circle
084         */
085        public double[] getCircle(double a, double b,
086        double c, double d, double e, double f) {
087            b=-b;
088            d=-d;
089            f=-f;
090            
091            double k = ((sq(a)+sq(b))*(e-c)
092            + (sq(c)+sq(d))*(a-e) + (sq(e)+sq(f))*(c-a)) /
093            (2*(b*(e-c)+d*(a-e)+f*(c-a))) ;
094            double h = ((sq(a)+sq(b))*(f-d) + (sq(c)+sq(d))*(b-f) +
095            (sq(e)+sq(f))*(d-b)) / (2*(a*(f-d)+c*(b-f)+e*(d-b)));
096            
097            double r = Math.sqrt(sq(a-h) + sq(b-k));
098            
099            return new double []{h, -k, 2*r};
100            
101        }  /**
102         * Returns the bounding circle for the binary image.
103         *
104         * Finds three points on the passed convex hull then
105         * calls the {@link #getCircle getCircle} method
106         *      @param XX int array of x coordinates for the convex hull
107         *      @param YY int array of y coordinates for the convex hull
108         *      @param ip ImageProcessor of binary image that the convex
109         *      hull is for
110         *      @return OvalRoi the bounding circle
111         */
112        public OvalRoi FindBoundingCircle(int[] XX, int[] YY, ImageProcessor ip) {
113            
114            //find the distance between the first two points
115            double max = Math.abs(Point2D.distance(
116            (double)XX[0], (double)YY[0], (double)XX[0], (double)YY[0]));
117            
118            double oldmax=max;
119            
120            int max_i = 0;
121            int max_i2= 0;
122            double min = 0;
123            //find the greatest distance between two points on the
124            //convex hull
125            for (int anchor=0; anchor<XX.length; anchor++) {
126                for (int i = 0; i< XX.length; i++) {
127                    double dist = Math.abs(Point2D.distance((double)XX[i],
128                    (double)YY[i], (double)XX[anchor], (double)YY[anchor]));
129                    max = Math.max(dist, max);
130                    if (max>oldmax)
131                    {oldmax=max;
132                     max_i=i;max_i2=anchor;}
133                    
134                }}
135            //draws the line
136            double x1=XX[max_i];
137            double y1=YY[max_i];
138            double x2=XX[max_i2];
139            double y2=YY[max_i2];
140            // ip.drawLine((int)x1, (int)y1, (int)x2, (int)y2);
141            majorcircle=(float)max;
142            //finds the midpoint
143            midx = (float)((x1+x2)/2.0);
144            midy =(float)((y1+y2)/2.0);
145            //  ip.drawDot((int)midx, (int)midy);
146            
147            //finds the maximum distance from the midpoint of
148            //that line to another point in the convex hull
149            max = Math.abs(Point2D.distance((double)XX[0],
150            (double)YY[0], midx, midy));
151            oldmax=max;
152            min=max;
153            int thirdpoint = max_i;
154            for (int i = 0; i< XX.length; i++) {
155                
156                {double dist = Math.abs(Point2D.distance((double)XX[i],
157                 (double)YY[i], midx, midy));
158                 max = Math.max(dist, max);
159                 min = Math.min(dist, min);
160                 if (max>oldmax)
161                 {oldmax=max;
162                  thirdpoint=i;
163                 }
164                }
165            }
166            double thirdx=XX[thirdpoint];
167            double thirdy=YY[thirdpoint];
168            boolean isdiameter=false;
169            if((max_i==thirdpoint)||(thirdpoint==max_i2)) {
170                isdiameter=true;
171            }
172            //decides if the point is on the current line
173            
174            //draws a line from the midpoint
175            //of the first line to a next furthest point on the convex hull
176            //ip.drawLine((int)midx, (int)midy, (int)XX[thirdpoint],
177            //(int)YY[thirdpoint]);
178            minorcircle=(float)max;
179            //Gets the bounding circle from the three points
180            //just found then draws it
181            double [] circ = getCircle(XX[max_i], YY[max_i],
182            XX[max_i2], YY[max_i2], thirdx, thirdy);
183            int [] Circ =
184            DoubleArrayToIntArray(circ, circ.length);
185            //IJ.showMessage("A"+Circ[0]+"A"+Circ[1]+"A"+Circ[2]);
186            OvalRoi boundingCircle = null;
187            
188            if (!isdiameter) {
189                diameter=Circ[2];
190                boundingCircle=
191                new OvalRoi(Circ[0]-Circ[2]/2,
192                Circ[1]-Circ[2]/2,
193                Circ[2], Circ[2]);
194                midx=Circ[0]-Circ[2]/2;
195                midy=Circ[1]-Circ[2]/2;
196            }
197            else {
198                diameter=(float)(2.0*max);
199                boundingCircle=
200                new OvalRoi((int)midx-(int)(max), (int)midy-(int)(max),
201                (int)(2*max), (int)(2*max));
202                midx = (int)midx-(int)(max);
203                midy=(int)midy-(int)(max);
204            }
205            
206            //ip.drawPolygon(boundingCircle.getPolygon());
207            return boundingCircle;
208        }
209        
210        
211        
212        /**
213         * ImageJ set up
214         * @param arg arguments to run plugin = null
215         * @param imp ImageProcessor on which to work
216         * @return int an integer
217         */
218        public int setup(String arg, ImagePlus imp) {
219            if (imp==null)
220                return(DONE);
221            else { stats=imp.getStatistics();
222            IJ.register(this.getClass());
223            filename=imp.getTitle();
224            return IJ.setupDialog(imp, DOES_8G);
225            }
226        }
227        
228        
229        
230        /**
231         * Draws the convex hull for the current ImageProcessor,
232         * makes some measurements, and draws the bounding circle
233         * on the image.  The bounding circle is currently
234         * drawn only; to access it, use the BoundingCircle
235         * roi made in this method.
236         * @param ip ImageProcessor on which to work
237         * @see GetDimensions
238         * @see getPolygon
239         * @see FindTriangle
240         */
241        
242        public void run(ImageProcessor ip) {
243            if (true) {
244                if (stats.histogram[0]>stats.histogram[255])
245                    foreground = 255;
246                else foreground = 0;
247                {//ImageStatistics stats = imp.getStatistics();
248         if (stats.histogram[0]+stats.histogram[255]!=stats.pixelCount) {
249             IJ.error("8-bit binary image (0 and 255) required.");
250             return;
251         }
252         
253    }
254                GetDimensions(ip);
255                PolygonRoi poly = this.getPolygon(ip);
256                img.setRoi(poly);
257                int ms = Analyzer.getMeasurements();
258                ms |= AREA+PERIMETER+CENTER_OF_MASS;//;+ELLIPSE;
259                Analyzer.setMeasurements(ms);
260                Analyzer ana = new Analyzer();
261                stats = img.getStatistics(ms);
262                Roi roi = img.getRoi();
263                ana.saveResults(stats, roi);
264                ResultsTable rt =Analyzer.getResultsTable();
265                
266                ip.drawPolygon(poly.getPolygon());
267                OvalRoi BoundingCircle = FindTriangle(X, Y, ip, poly);
268                ip.drawPolygon(BoundingCircle.getPolygon());
269                
270                int cr = rt.getCounter();
271                double area = rt.getValue(ResultsTable.AREA, cr-1);
272                double perimeter = rt.getValue(ResultsTable.PERIMETER, cr-1);
273                double circul = (perimeter*perimeter)/area;
274                rt.addValue("Circularity",
275                perimeter==0.0?0.0:4.0*Math.PI*(area/(perimeter*perimeter)));
276                rt.addValue("Roundness", circul);
277                rt.addLabel("FileName", filename);
278                //            float major = rt.getValue(ResultsTable.MAJOR, cr-1);
279                //            float minor = rt.getValue(ResultsTable.MINOR, cr-1);
280                //            rt.addValue("Axis1", major);
281                //            rt.addValue("Axis2", minor);
282                img.killRoi();
283                //Rectangle rect =poly.getBoundingRect();
284                ////         ip.drawPolygon(poly.getPolygon());
285                ////         OvalRoi BoundingCircle = FindTriangle(X, Y, ip, poly);
286                ////         ip.drawPolygon(BoundingCircle.getPolygon());
287                rt.addValue("Bounding Circle Diameter", diameter);
288                ana.displayResults();
289                ana.updateHeadings();
290            }
291        }
292        
293        /**Returns the square of the passed variable.
294         *@param N a double
295         *@return N*N
296         */
297        public double sq(double N){ return (N*N);}
298        
299        
300        
301        
302        
303        
304        
305        
306        /**
307         * This method determines the vertices of a polygon which forms a convex
308         * hull around the image. It does this by first mapping the vertical
309         * contours of the image. (Horizontal or vertical contours could have
310         * been used, but only one is necessary.) After they are mapped, all of
311         * the points which can be enclosed by connecting any other points are
312         * eliminated. This leaves only the outermost points, the ones that form
313         * the convex hull. Returns a polygon created with these points.
314         * @param ip ImageProcessor to work on
315         * @return PolygonRoi the convex hull
316         *@see mapTopContours
317         *@see mapBottomContours
318         *@see removeContours
319         *@author Thomas R. Roy, University of Alberta
320         */
321        public PolygonRoi getPolygon(ImageProcessor ip){
322            
323            Point coords[] = new Point[maxImageWidth * 2];
324            for (int i = 0; i < maxImageWidth * 2; i++)
325                coords[i] = new Point();
326            int[] histogram = new int[256];
327            int vertCount=0;
328            int y, x;
329            Point farLeft, farRight;
330            
331            //Start by mapping farthest left point
332            x = this.left;
333            y=top-2;
334            do{
335                y++;
336                ip.setRoi(x, y, 1, 1);
337                histogram = ip.getHistogram();
338            } while (( histogram[ this.foreground ]==0 ) && y<=bottom);
339            farLeft = new Point(x,y);
340            int index = x;
341            
342            coords[0].x=index;
343            coords[0].y=y;
344            
345            //Find the furthest right point
346            x = this.right;
347            y=top-2;
348            do{
349                y++;
350                ip.setRoi(x, y, 1, 1);
351                histogram = ip.getHistogram();
352            } while (( histogram[ this.foreground ]==0 ) && y<=bottom);
353            farRight = new Point(x,y);
354            
355            //Map the top and bottom contours
356            vertCount = mapTopContours(farLeft, farRight, coords, vertCount, ip);
357            int endPoint = vertCount;
358            vertCount = mapBottomContours(farLeft, farRight, coords, vertCount, ip);
359            
360            vertCount++;
361            coords[vertCount]=coords[0];
362            
363            //Remove the concave contours, leaving only concave ones
364            coords = removeContours(coords, vertCount, endPoint);
365            
366            //Produce an array for the output
367            X = new int [coords.length];
368            Y = new int [coords.length];
369            for (int i = 0; i < coords.length; i++) {
370                X[i]=coords[i].x;
371                Y[i]=coords[i].y;
372            }
373            return new PolygonRoi(X, Y, coords.length, img,Roi.POLYGON);
374        }
375        /**Maps the bottom contours of the image.
376         @param farLeft Point
377         @param farRight Point
378         @param coords Point[]
379         @param vertCount int
380         @param ip ImageProcessor
381         @return int for vertical count*/
382        public int mapBottomContours(Point farLeft, Point farRight,
383        Point[] coords, int vertCount, ImageProcessor ip){
384            //Begin mapping bottom contours, right to left
385            int[] histogram = new int[256];
386            
387            for (int index = farRight.x; index >= farLeft.x; index--){
388                int y = this.bottom;
389                do{
390                    y--;
391                    ip.setRoi(index, y, 1, 1);
392                    histogram = ip.getHistogram();
393                } while (( histogram[ this.foreground ]==0 ) && y>=top);
394                
395                if (y>top){
396                    vertCount++;
397                    coords[vertCount].x = index;
398                    coords[vertCount].y=y;
399                }
400            }
401            return vertCount;
402            
403        }
404        /**Maps top contours.
405         *@param farLeft Point
406         *@param farRight Point
407         *@param coords Point[]
408         *@param vertCount int
409         *@param ip ImageProcessor
410         *@return int for top count*/
411        public int mapTopContours(Point farLeft, Point farRight,
412        Point[] coords, int vertCount, ImageProcessor ip){
413            //Begin mapping contours left to right
414            int[] histogram = new int[256];
415            
416            for (int index = farLeft.x; index <= farRight.x; index++){
417                
418                int y = this.top;
419                do{
420                    y++;
421                    ip.setRoi(index, y, 1, 1);
422                    histogram = ip.getHistogram();
423                } while (( histogram[ this.foreground ]==0 ) && y<=(bottom));
424                
425                if (y<bottom){
426                    vertCount++;
427                    coords[vertCount].x = index;
428                    coords[vertCount].y = y;
429                }
430            }
431            return vertCount;
432            
433        }
434         /**
435           * This method removes contours that contain concave areas.
436           * The algorithm tests each contour line to ensure that it has a greater
437           * slope than all of the lines after it. If this is not the case, then it
438           * replaces the endpoint with the endpoint of the next contour pixel, and
439           * checks again, until it has removed all of the contour points that are
440           * concave.
441           * @return Point[] array of new coordinates
442           */
443        public Point[] removeContours(Point coords[], int length, int furthest){
444         
445            int index=0;
446            int nIndex = 0;
447            float rise, run, slope, slope2;
448            
449            //First check the top contours
450            do{
451                float highest = 0;
452                int highestIndex = -1;
453                for(int i = index+1; i <= furthest; i++){
454                    run = coords[i].x - coords[index].x;
455                    rise = coords[i].y - coords[index].y;
456                    if (run == 0)
457                        slope = 100000;
458                    else
459                        slope = rise/run;
460                    
461                    if (slope <= highest || highestIndex == -1){
462                        highest = slope;
463                        highestIndex = i;
464                    }
465                }
466                coords[nIndex] = coords[index];
467                nIndex++;
468                index = highestIndex;
469                
470            } while (index != -1);
471            
472            //Now check the bottom contours
473            index=furthest;
474            do{
475                float highest = 0;
476                int highestIndex = -1;
477                for(int i = index+1; i < length; i++){
478                    run = coords[i].x - coords[index].x;
479                    rise = coords[i].y - coords[index].y;
480                    if (run == 0)
481                        slope = 100000;
482                    else
483                        slope = rise/run;
484                    
485                    if (slope <= highest || highestIndex == -1){
486                        highest = slope;
487                        highestIndex = i;
488                    }
489                }
490                coords[nIndex] = coords[index];
491                nIndex++;
492                index = highestIndex;
493                
494            } while (index != -1);
495            
496            Point newCoords[] = new Point[nIndex];
497            for (int i = 0; i < nIndex; i++)
498                newCoords[i] = coords[i];
499            
500            return newCoords;
501        }
502        
503        
504      /**Determine the dimensions of the image,
505      *       and sets some of the important variables.
506      *@param ip ImageProcessor on which to operate
507       */  
508        public void GetDimensions(ImageProcessor ip) {
509            
510            
511            img = WindowManager.getCurrentImage();
512            int[] histogram = new int[256];/*make an array for histogram*/
513            this.width = ip.getWidth();/*record the width and height*/
514            this.height = ip.getHeight();
515            Rectangle roi;
516            
517            //start at the leftmost edge, move to the middle,
518            //and find the first coloured pixel for the left edge
519            this.left = -1;
520            do
521            {       this.left++;
522                    ip.setRoi(this.left, 0, 1, this.height);
523                    histogram = ip.getHistogram();
524            } while ( histogram[this.foreground]==0 );
525            
526            //start at the top and find the first pixel
527            this.top = -1;
528            do
529            {       this.top++;
530                    ip.setRoi(this.left, this.top, this.width, 1);
531                    histogram = ip.getHistogram();
532            } while (histogram[this.foreground]==0);
533            
534            //start at the right and find the first pixel
535            right =width-1;
536            do
537            {       this.right--;
538                    ip.setRoi(this.right, this.top, 1, this.height);
539                    histogram = ip.getHistogram();
540            } while (histogram[this.foreground]==0);
541            
542            //start at the bottom of the image and find the first pixel*/
543            this.bottom =this.height;
544            do
545            {       this.bottom--;
546                    ip.setRoi(this.left, this.bottom, this.right, 1);
547                    histogram = ip.getHistogram();
548            } while (histogram[this.foreground]==0);
549            // This part of the methods finds the coordinates
550            //of the top, bottom, right and left pixels,
551            // which is used later for the polygon.
552            this.leftyc = this.top-1;
553            do
554            {       this.leftyc++;
555                    ip.setRoi(this.left, this.leftyc, 1, 1);
556                    histogram = ip.getHistogram();
557            } while (histogram[this.foreground]==0);
558            
559            this.topxc = this.left-1;
560            do
561            {       topxc++;
562                    ip.setRoi(topxc, top, 1, 1);
563                    histogram = ip.getHistogram();
564            } while (histogram[this.foreground]==0);
565            
566            this.rightyc = this.top-1;
567            do
568            {       this.rightyc++;
569                    ip.setRoi(this.right, this.rightyc, 1, 1);
570                    histogram = ip.getHistogram();
571            } while (histogram[this.foreground]==0);
572            
573            this.bottomxc = this.right+1;
574            do
575            {       this.bottomxc--;
576                    ip.setRoi(this.bottomxc, this.bottom, 1, 1);
577                    histogram = ip.getHistogram();
578            } while (histogram[this.foreground]==0);
579            
580            //We have to ensure these regions don't overlap.
581            this.top--;
582            this.bottom++;
583            
584            this.imgHeight = bottom - top;
585            this.imgWidth = right - left;
586            //this.centrex = (float)(left + (this.imgWidth/2));
587            //this.centrex = (float)(top + (this.imgHeight/2));
588        }
589        
590        /**
591         * Finds the triangle having the greatest perimeter using
592         * all points in the convex hull.  Then uses this as the three
593         * points on the hull that define the circle. Is an alternative
594         * when Draye's method yields a triangle that does not include all
595         * of the points.
596         * @return OvalRoi a circle as an OvalRoi
597         * @param XX array of x coordinates for the hull
598         * @param YY array of y coordinates for the hull
599         * @param ip ImageProcessor on which to work
600         * @param poly PolygonRoi for the convex hull
601         * @see FindBoundingCircle
602         */
603        public OvalRoi FindTriangle(int[] XX, int[] YY,
604        ImageProcessor ip, PolygonRoi poly) {
605            //find perimeter of each triangle
606            
607            OvalRoi boundingCircle = null;
608            boundingCircle=FindBoundingCircle(XX, YY, ip);
609            boolean AllIn=true;
610            for (int i = 0; i < XX.length; i++) {
611                if
612                (!(boundingCircle.getPolygon().contains(new
613                Point(poly.getXCoordinates()[i],
614                poly.getYCoordinates()[i]))))
615                    if(!boundingCircle.getPolygon().intersects(XX[i]-1, YY[i]-1,
616                    3,3))
617                    {AllIn=false;
618                     //IJ.showMessage("not at"+i);
619                    }
620            }
621            if (AllIn==false)
622            { double p = 0;
623              //find the distance between the first two points
624              double oldmin = Math.abs(Point2D.distance(
625              (double)XX[0], (double)YY[0], (double)XX[0], (double)YY[0]));
626              
627              double min=oldmin;
628              
629              int max_i = 0;
630              int max_i2= 0;
631              
632              //find the greatest distance between two points on the
633              //convex hull
634              for (int anchor=0; anchor<XX.length; anchor++) {
635                  for (int i = 0; i< XX.length; i++) {
636                      double dist = Math.abs(Point2D.distance((double)XX[i],
637                      (double)YY[i], (double)XX[anchor], (double)YY[anchor]));
638                      min = Math.max(dist, min);
639                      if (min>oldmin)
640                      {oldmin=min;
641                       max_i=i;max_i2=anchor;}
642                      
643                  }}
644              int I=0, J=0, A=0;
645              double maxdist=0;
646              
647              for (int anchor=0; anchor<XX.length; anchor++) {
648                  for (int i = 0; i< XX.length; i++) {
649                      for (int j = 0; j< XX.length; j++) {
650                          double dist1 = Math.abs(Point2D.distance((double)XX[i],
651                          (double)YY[i], (double)XX[anchor], (double)YY[anchor]));
652                          double dist2 = Math.abs(Point2D.distance((double)XX[j],
653                          (double)YY[j], (double)XX[anchor], (double)YY[anchor]));
654                          double dist3 = Math.abs(Point2D.distance((double)XX[i],
655                          (double)YY[i], (double)XX[j], (double)YY[j]));
656                          if (true)//((dist1>=min)||(dist2>=min)||(dist3>=min))
657                          { double newdist=dist1+dist2+dist3;
658                            if (newdist>maxdist){
659                                maxdist=newdist;
660                                I=i;
661                                J=j;
662                                A=anchor;
663                                double [] circ = getCircle(XX[I], YY[I],
664                                XX[J], YY[J], XX[A], YY[A]);
665                                int [] Circ =
666                                DoubleArrayToIntArray(circ, circ.length);
667                              //IJ.showMessage("A"+Circ[0]+"A"+Circ[1]+"A"+Circ[2]);
668                                
669                                
670                                if (true) {
671                                    diameter=Circ[2];
672                                    boundingCircle=
673                                    new OvalRoi(Circ[0]-Circ[2]/2,
674                                    Circ[1]-Circ[2]/2,
675                                    Circ[2], Circ[2]);
676                                    midx=Circ[0]-Circ[2]/2;
677                                    midy=Circ[1]-Circ[2]/2;
678                                }
679                            }   }
680                      }
681                  }
682              }
683              
684            }
685            return boundingCircle;
686        }
687        
688        
689        
690    }//end class