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 Ch10_Line_Fitting; 010 011import ij.IJ; 012import ij.ImagePlus; 013import ij.gui.GenericDialog; 014import ij.gui.Overlay; 015import ij.gui.Roi; 016import ij.plugin.filter.PlugInFilter; 017import ij.process.ImageProcessor; 018import imagingbook.common.color.sets.BasicAwtColor; 019import imagingbook.common.geometry.basic.Pnt2d; 020import imagingbook.common.geometry.fitting.line.LineFit; 021import imagingbook.common.geometry.fitting.line.LinearRegressionFit; 022import imagingbook.common.geometry.fitting.line.OrthogonalLineFitEigen; 023import imagingbook.common.geometry.line.AlgebraicLine; 024import imagingbook.common.hough.HoughLine; 025import imagingbook.common.ij.DialogUtils; 026import imagingbook.common.ij.IjUtils; 027import imagingbook.common.ij.RoiUtils; 028import imagingbook.common.ij.overlay.ColoredStroke; 029import imagingbook.common.ij.overlay.ShapeOverlayAdapter; 030import imagingbook.core.jdoc.JavaDocHelp; 031 032import static imagingbook.common.ij.IjUtils.noCurrentImage; 033 034/** 035 * <p> 036 * Performs line fitting on the point set specified by the current ROI. Two fitting methods are employed: (a) linear 037 * regression fitting, (b) orthogonal regression fitting. The result of the first varies with rotation, while orthogonal 038 * fitting is rotation-invariant. See Sec. 10.2 (Fig. 10.4) of [1] for additional details. Sample points are either 039 * collected from the ROI (if available) or collected as foreground pixels (values > 0) from the image. If no image 040 * is currently open, the user is asked to create a suitable sample image. 041 * </p> 042 * <p> 043 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing – An Algorithmic Introduction</em>, 3rd ed, Springer 044 * (2022). 045 * </p> 046 * 047 * @author WB 048 * @version 2022/10/03 049 */ 050public class Line_Fitting implements PlugInFilter, JavaDocHelp { // TODO: activate dialog 051 052 public static BasicAwtColor RegressionFitColor = BasicAwtColor.Red; 053 public static BasicAwtColor OrthogonalFitColor = BasicAwtColor.Blue; 054 static boolean UsePointsFromRoi = false; 055 public boolean ShowLog = true; 056 057 public static double StrokeWidth = 0.5; 058 059 private ImagePlus im; 060 061 /** 062 * Constructor, asks to open a predefined sample image if no other image 063 * is currently open. 064 */ 065 public Line_Fitting() { 066 if (noCurrentImage()) { 067 if (DialogUtils.askForSampleImage()) { 068 IjUtils.run(new Line_Make_Random()); //runPlugIn(Line_Make_Random.class); 069 } 070 } 071 } 072 073 // ------------------------------------------------------------------------------------ 074 075 @Override 076 public int setup(String arg, ImagePlus im) { 077 this.im = im; 078 return DOES_ALL; 079 } 080 081 @Override 082 public void run(ImageProcessor ip) { 083 int width = ip.getWidth(); 084 int height = ip.getHeight(); 085 086 Roi roi = im.getRoi(); 087 UsePointsFromRoi = (roi != null); 088 089 if (!runDialog()) { 090 return; 091 } 092 093 Pnt2d[] points = (UsePointsFromRoi) ? 094 RoiUtils.getOutlinePointsFloat(roi) : 095 IjUtils.collectNonzeroPoints(ip); 096 097 IJ.log("Found points " + points.length); 098 if (points.length < 2) { 099 IJ.error("At least 2 points are required, but found only " + points.length); 100 return; 101 } 102 103 Overlay oly = im.getOverlay(); 104 if (oly == null) { 105 oly = new Overlay(); 106 im.setOverlay(oly); 107 } 108 ShapeOverlayAdapter ola = new ShapeOverlayAdapter(oly); 109 110 // ------------------------------------------------------------------------ 111 LineFit fitO = new OrthogonalLineFitEigen(points); 112 AlgebraicLine lineO = fitO.getLine(); 113 // ------------------------------------------------------------------------ 114 115 if (lineO == null) { 116 IJ.log("Orthogonal line fit failed!"); 117 return; 118 } 119 120 if (ShowLog) { 121 IJ.log("Orthogonal line fit: " + lineO.toString()); 122 } 123 124 ColoredStroke orthogonalStroke = new ColoredStroke(StrokeWidth, OrthogonalFitColor.getColor()); 125 ola.addShape(new HoughLine(lineO).getShape(width, height), orthogonalStroke); 126 127 // ------------------------------------------------------------------------ 128 LineFit fitR = new LinearRegressionFit(points); 129 AlgebraicLine lineR = fitR.getLine(); 130 // ------------------------------------------------------------------------ 131 132 if (lineR == null) { 133 IJ.log("Regression fit failed!"); 134 return; 135 } 136 137 if (ShowLog) { 138 IJ.log("Regression line fit: " + lineR.toString()); 139 } 140 141 ColoredStroke regressionStroke = new ColoredStroke(StrokeWidth, RegressionFitColor.getColor()); 142 ola.addShape(new HoughLine(lineR).getShape(width, height), regressionStroke); 143 } 144 145 // ------------------------------------------ 146 147 private boolean runDialog() { 148 GenericDialog gd = new GenericDialog(this.getClass().getSimpleName()); 149 gd.addHelp(getJavaDocUrl()); 150 gd.addMessage(DialogUtils.formatText(50, 151 "This plugin performs algebraic + geometric circle fitting,", 152 "either to ROI points (if available) or foreground points", 153 "collected from the pixel image." 154 )); 155 156 gd.addCheckbox("Use ROI (float) points", UsePointsFromRoi); 157 gd.addEnumChoice("Regression fit color", RegressionFitColor); 158 gd.addEnumChoice("Orthogonal fit color", OrthogonalFitColor); 159 160 gd.showDialog(); 161 if (gd.wasCanceled()) 162 return false; 163 164 UsePointsFromRoi = gd.getNextBoolean(); 165 RegressionFitColor = gd.getNextEnumChoice(BasicAwtColor.class); 166 OrthogonalFitColor = gd.getNextEnumChoice(BasicAwtColor.class); 167 168 return true; 169 } 170}