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 Ch12_Ransac_Hough;
010
011import Ch12_Ransac_Hough.settings.RansacDrawSettings;
012import ij.IJ;
013import ij.ImagePlus;
014import ij.gui.GenericDialog;
015import ij.plugin.ImagesToStack;
016import ij.plugin.filter.PlugInFilter;
017import ij.process.ByteProcessor;
018import ij.process.ImageProcessor;
019import imagingbook.common.geometry.basic.Pnt2d;
020import imagingbook.common.geometry.line.AlgebraicLine;
021import imagingbook.common.ij.DialogUtils;
022import imagingbook.common.ij.IjUtils;
023import imagingbook.common.ij.overlay.ColoredStroke;
024import imagingbook.common.ij.overlay.ShapeOverlayAdapter;
025import imagingbook.common.ransac.RansacLineDetector;
026import imagingbook.common.ransac.RansacResult;
027import imagingbook.core.jdoc.JavaDocHelp;
028import imagingbook.sampleimages.GeneralSampleImage;
029
030import java.util.ArrayList;
031import java.util.List;
032
033import static imagingbook.common.ij.DialogUtils.addToDialog;
034import static imagingbook.common.ij.DialogUtils.getFromDialog;
035import static imagingbook.common.ij.IjUtils.noCurrentImage;
036
037/**
038 * <p>
039 * RANSAC line detection using imagingbook library class {@link RansacLineDetector} (see Sec. 12.1.2 of [1] for
040 * details). If no image is currently open, the plugin optionally loads a suitable sample image.
041 * </p>
042 * <p>
043 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing &ndash; An Algorithmic Introduction</em>, 3rd ed, Springer
044 * (2022).
045 * </p>
046 *
047 * @author WB
048 * @version 2022/10/03
049 */
050public class Ransac_Line_Detect implements PlugInFilter, RansacDrawSettings, JavaDocHelp {
051        
052        private static RansacLineDetector.Parameters params = new RansacLineDetector.Parameters();
053        static {
054                params.randomSeed = 17;
055        }
056        
057        private static int MaxLineCount = 6;
058        
059        // --------------------
060        
061        private int W, H;
062        private ImagePlus im;
063        private String title;
064
065        /**
066         * Constructor, asks to open a predefined sample image if no other image
067         * is currently open.
068         */
069        public Ransac_Line_Detect() {
070                if (noCurrentImage()) {
071                        DialogUtils.askForSampleImage(GeneralSampleImage.NoisyLines);
072                }
073        }
074        
075        @Override
076        public int setup(String arg, ImagePlus im) {
077                this.im = im;
078                return DOES_8G + NO_CHANGES;
079        }
080
081        @Override
082        public void run(ImageProcessor ip) {
083                title = "Lines from " + im.getTitle();
084                W = ip.getWidth();
085                H = ip.getHeight();
086                
087                if (!runDialog()) {
088                        return;
089                }
090                
091                Pnt2d[] points = IjUtils.collectNonzeroPoints(ip);
092                List<RansacResult<AlgebraicLine>> lines = new ArrayList<>();
093
094                // ---------------------------------------------------------------
095                RansacLineDetector detector = new RansacLineDetector();
096                // ---------------------------------------------------------------
097                
098                List<ImagePlus> resultImages = new ArrayList<>();
099                int cnt = 0;
100                
101                RansacResult<AlgebraicLine> sol = detector.detectNext(points);
102                while (sol != null && cnt < MaxLineCount) {
103                        lines.add(sol);
104                        cnt = cnt + 1;
105                        
106                        ImagePlus imSnap = new ImagePlus("line-"+cnt, showPointSet(points));
107                        ShapeOverlayAdapter ola = new ShapeOverlayAdapter();
108
109                        {       // draw inliers (points)
110                                ColoredStroke stroke = new ColoredStroke(LineStrokeWidth, InlierColor, 0);
111                                stroke.setFillColor(InlierColor);
112                                for (Pnt2d p : sol.getInliers()) {
113                                        ola.addShape(p.getShape(InlierRadius), stroke);
114                                }
115                        }
116        
117                        {       // draw initial line
118                                AlgebraicLine line = sol.getPrimitiveInit();
119                                ColoredStroke stroke = new ColoredStroke(LineStrokeWidth, InitialFitColor, 0);
120                                ola.addShape(line.getShape(W, H), stroke);
121                        }
122        
123                        {       // draw final line
124                                AlgebraicLine line = sol.getPrimitiveFinal();
125                                ColoredStroke stroke = new ColoredStroke(LineStrokeWidth, FinalFitColor, 0);
126                                ola.addShape(line.getShape(W, H), stroke);
127                        }
128        
129                        {       // draw the 2 random points used
130                                ColoredStroke pointStroke = new ColoredStroke(LineStrokeWidth, RandomDrawDotColor, 0);
131                                pointStroke.setFillColor(RandomDrawDotColor);
132                                for (Pnt2d p : sol.getDraw()) {
133                                        ola.addShape(p.getShape(RandoDrawDotRadius), pointStroke);
134                                }
135                        }
136
137                        imSnap.setOverlay(ola.getOverlay());
138                        resultImages.add(imSnap);
139                        sol = detector.detectNext(points);
140                }
141
142                // combine all result images to a stack:
143                if (resultImages.isEmpty()) {
144                        IJ.error("No items detected!");
145                }
146                else {
147                        ImagePlus stack = ImagesToStack.run(resultImages.toArray(new ImagePlus[0]));
148                        stack.setTitle(title);
149                        stack.show();
150                }
151        }
152
153        private ByteProcessor showPointSet(Pnt2d[] points) {
154                ByteProcessor bp = new ByteProcessor(W, H);
155                IjUtils.drawPoints(bp, points, 255);
156                bp.invertLut();
157                return bp;
158        }
159        
160        // ------------------------------------------------------------------------
161        
162        private boolean runDialog() {
163                GenericDialog gd = new GenericDialog(this.getClass().getSimpleName());
164                gd.addHelp(getJavaDocUrl());
165                addToDialog(params, gd);
166                gd.addNumericField("Max. number of lines", MaxLineCount);
167                
168                gd.addStringField("Output title", title, 16);
169                
170                gd.showDialog();
171                if (gd.wasCanceled())
172                        return false;
173                
174                getFromDialog(params, gd);
175                MaxLineCount = (int) gd.getNextNumber();
176                title = gd.getNextString();
177                
178                return params.validate();
179        }
180
181}