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