001/*******************************************************************************
002 * This software is provided as a supplement to the authors' textbooks on digital
003 * image processing published by Springer-Verlag in various languages and editions.
004 * Permission to use and distribute this software is granted under the BSD 2-Clause
005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause).
006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved.
007 * Visit https://imagingbook.com for additional details.
008 ******************************************************************************/
009package Ch11_Circle_Ellipse_Fitting;
010
011import ij.ImagePlus;
012import ij.gui.GenericDialog;
013import ij.gui.NewImage;
014import ij.plugin.PlugIn;
015import imagingbook.common.geometry.basic.Pnt2d;
016import imagingbook.common.geometry.ellipse.GeometricEllipse;
017import imagingbook.common.geometry.ellipse.project.ConfocalConicEllipseProjector;
018import imagingbook.common.geometry.ellipse.project.EllipseProjector;
019import imagingbook.common.geometry.ellipse.project.OrthogonalEllipseProjector;
020import imagingbook.common.ij.DialogUtils;
021import imagingbook.common.ij.overlay.ColoredStroke;
022import imagingbook.common.ij.overlay.ShapeOverlayAdapter;
023import imagingbook.core.jdoc.JavaDocHelp;
024
025import java.awt.Color;
026import java.awt.geom.Path2D;
027import java.util.Random;
028
029import static java.lang.Math.PI;
030
031/**
032 * <p>
033 * This plugin creates a new image with an ellipse and a set of random points. For each point, the closest (contact)
034 * point on the ellipse is calculated and a connecting line is drawn to a vector overlay. Two closest-point algorithms
035 * are available (see Secs. 11.2.2 and 11.2.3 of [1] for additional details):
036 * </p>
037 * <ol>
038 * <li> orthogonal, exact closest point (exact but iterative) and </li>
039 * <li> approximate confocal conic closest point estimation (only approximate but non-iterative).</li>
040 * </ol>
041 * <p>
042 * The random seed may be specified for repeatability of experiments.
043 * </p>
044 * <p>
045 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing &ndash; An Algorithmic Introduction</em>,
046 * 3rd ed, Springer (2022).
047 * </p>
048 *
049 * @author WB
050 */
051public class Ellipse_Closest_Points_Demo implements PlugIn, JavaDocHelp {
052        
053        private static int W = 400;
054        private static int H = 400;
055        private static int N = 250;
056        private static int Seed = 0;
057        
058        private static final double StrokeWidth = 1.0;
059        private static final Color EllipseColor = Color.green;
060        private static final Color PointColor = Color.blue;
061        private static ClosestPointAlgorithm Algorithm = ClosestPointAlgorithm.Orthogonal;
062        
063        public enum ClosestPointAlgorithm {
064                Orthogonal, ConfocalConics
065        }
066        
067        @Override
068        public void run(String arg) {
069                
070                if (!runDialog()) {
071                        return;
072                }
073                
074                //GeometricEllipse realEllipse = new GeometricEllipse(170, 120, 200, 190, PI/7);                // example 1
075                GeometricEllipse realEllipse = new GeometricEllipse(1100, 120, 25, 1200, PI/2 + 0.2);   // example 2
076                
077                String title = this.getClass().getSimpleName() + " (" + Algorithm.toString() + ")";
078                ImagePlus im = NewImage.createByteImage(title, W, H, 1, NewImage.FILL_WHITE);
079                
080                ShapeOverlayAdapter ola = new ShapeOverlayAdapter();
081                
082                ColoredStroke ellipseStroke = new ColoredStroke(StrokeWidth, EllipseColor);
083                ColoredStroke pointStroke = new ColoredStroke(StrokeWidth, PointColor);
084                ola.addShapes(realEllipse.getShapes(), ellipseStroke);
085                
086                EllipseProjector projector = null;
087                switch (Algorithm) {
088                case Orthogonal:
089                        projector = new OrthogonalEllipseProjector(realEllipse);
090                        break;
091                case ConfocalConics:
092                        projector = new ConfocalConicEllipseProjector(realEllipse);
093                        break;
094                }
095                
096                Random rd = (Seed == 0) ? new Random() : new Random(Seed);
097
098                for (int i = 0; i < N; i++) {
099                        Pnt2d p = Pnt2d.from(W * rd.nextDouble(), H* rd.nextDouble());
100                        Pnt2d p0 = projector.project(p);
101                        // draw connecting line:
102                        Path2D path = new Path2D.Double();
103                        path.moveTo(p.getX(), p.getY());
104                        path.lineTo(p0.getX(), p0.getY());
105                        ola.addShape(path);
106                        // draw point p:
107                        ola.addShape(p.getShape(), pointStroke);
108                }
109                
110                im.setOverlay(ola.getOverlay());
111                im.show();
112        }
113
114        private boolean runDialog() {
115                GenericDialog gd = new GenericDialog(this.getClass().getSimpleName());
116                gd.addHelp(getJavaDocUrl());
117                gd.addMessage(DialogUtils.formatText(50,
118                                "This plugin creates a new image with an ellipse and a",
119                                "set of random points. For each point, the closest (contact)",
120                                "point on the ellipse is calculated and a connecting line",
121                                "is drawn to a vector overlay."
122                                ));
123                
124                gd.addNumericField("Number of random points", N, 0);
125                gd.addNumericField("Random seed (0 = none)", Seed, 0);
126                gd.addEnumChoice("Closest-point method", Algorithm);
127                
128                gd.showDialog();
129                if (gd.wasCanceled())
130                        return false;
131                
132                N = (int) gd.getNextNumber();
133                Seed = (int) gd.getNextNumber();
134                Algorithm = gd.getNextEnumChoice(ClosestPointAlgorithm.class);
135                
136                return true;
137        }
138        
139}