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 More_;
010
011import ij.IJ;
012import ij.ImagePlus;
013import ij.gui.GenericDialog;
014import ij.gui.Overlay;
015import ij.plugin.filter.PlugInFilter;
016import ij.process.ImageProcessor;
017import imagingbook.common.corners.Corner;
018import imagingbook.common.corners.GradientCornerDetector.Parameters;
019import imagingbook.common.corners.HarrisCornerDetector;
020import imagingbook.common.geometry.basic.Pnt2d;
021import imagingbook.common.geometry.delaunay.DelaunayTriangulation;
022import imagingbook.common.geometry.delaunay.Triangle;
023import imagingbook.common.ij.DialogUtils;
024import imagingbook.common.ij.overlay.ColoredStroke;
025import imagingbook.common.ij.overlay.ShapeOverlayAdapter;
026import imagingbook.core.jdoc.JavaDocHelp;
027import imagingbook.core.resource.ImageResource;
028import imagingbook.sampleimages.GeneralSampleImage;
029
030import java.awt.BasicStroke;
031import java.awt.Color;
032import java.util.List;
033
034import static imagingbook.common.ij.IjUtils.noCurrentImage;
035
036
037/**
038 * This ImageJ plugin performs corner detection on the active image, applies Delaunay triangulation to the N
039 * strongest corners and displays the result as a vector overlay on top of the same image. If no image is currently
040 * open, the user is asked to load a predefined sample image.
041 *
042 * @author WB
043 * @version 2022/06/25
044 * @see HarrisCornerDetector
045 * @see DelaunayTriangulation
046 */
047public class Delaunay_Demo implements PlugInFilter, JavaDocHelp {
048        
049        private static ImageResource SampleImage = GeneralSampleImage.MortarSmall;
050        
051        private static int CornerCount = 0;                                     // number of corners to show (0 = show all)
052        private static boolean ShuffleCorners = true;   
053        private static Color DelaunayColor = Color.green;       // color for graph edges
054        private static Color PointColor = Color.red;            // color for point markers
055        private static double StrokeWidth = 0.25;
056        private static double PointRadius = 1.0;
057        
058        private static Parameters params = new Parameters();
059        
060        private ImagePlus im = null;
061
062        /**
063         * Constructor, asks to open a predefined sample image if no other image is currently open.
064         */
065        public Delaunay_Demo() {
066                if (noCurrentImage()) {
067                        DialogUtils.askForSampleImage(SampleImage);
068                }
069        }
070        
071    @Override
072        public int setup(String arg, ImagePlus im) {
073        this.im = im;
074        return DOES_ALL;
075    }
076    
077    @Override
078        public void run(ImageProcessor ip) {
079        
080                if (!runDialog()) {
081                        return;
082                }
083                
084                HarrisCornerDetector cd = new HarrisCornerDetector(ip, params);
085                List<Corner> corners = cd.getCorners();
086
087                DelaunayTriangulation dt = DelaunayTriangulation.from(corners, ShuffleCorners);
088                im.setOverlay(makeOverlay(dt));
089    }
090    
091        // ---------------------------------------------------------------------------
092        
093        private Overlay makeOverlay(DelaunayTriangulation dt) {
094                List<Triangle> triangles = dt.getTriangles();
095                List<Pnt2d> allPoints = dt.getPoints();
096
097                ShapeOverlayAdapter ola = new ShapeOverlayAdapter();
098
099                // draw the triangles:
100                ColoredStroke triangleStroke = new ColoredStroke(StrokeWidth, DelaunayColor);
101                triangleStroke.setEndCap(BasicStroke.CAP_ROUND);
102                triangleStroke.setLineJoin(BasicStroke.JOIN_ROUND);
103                
104                ola.setStroke(triangleStroke);
105                for (Triangle trgl : triangles) {
106                         ola.addShape(trgl.getShape());
107                }
108                
109                // draw the vertices of the triangulation:
110                ColoredStroke pointStroke = new ColoredStroke(StrokeWidth, PointColor);
111                ola.setStroke(pointStroke);
112                for (Pnt2d p : allPoints) {
113                        // get original Pnt2d shape (not Corner shape)
114                        ola.addShape(Pnt2d.from(p).getShape(PointRadius));
115                }
116
117                return ola.getOverlay();
118        }
119        
120        // ---------------------------------------------------------------------------
121        
122        private boolean runDialog() {
123                GenericDialog gd = new GenericDialog(this.getClass().getSimpleName());
124                gd.addHelp(getJavaDocUrl());
125                gd.addNumericField("Corner score threshold", params.scoreThreshold, 0);
126                gd.addNumericField("Border width", params.border, 0);
127                gd.addCheckbox("Clean up corners", params.doCleanUp);
128                gd.addNumericField("Corners to show (0 = show all)", CornerCount, 0);
129                gd.addCheckbox("Shuffle corners", ShuffleCorners);
130                
131                gd.showDialog();
132                if(gd.wasCanceled())
133                        return false;   
134                
135                params.scoreThreshold = (int) gd.getNextNumber();
136                params.border = (int) gd.getNextNumber();
137                params.doCleanUp = gd.getNextBoolean();
138                CornerCount = (int) gd.getNextNumber();
139                ShuffleCorners = gd.getNextBoolean();
140                
141                if(gd.invalidNumber() || !params.validate()) {
142                        IJ.error("Input Error", "Invalid input");
143                        return false;
144                }       
145                return true;
146        }
147}