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.ImagePlus;
012import ij.plugin.ImagesToStack;
013import ij.plugin.PlugIn;
014import ij.process.ByteProcessor;
015import imagingbook.common.geometry.basic.Pnt2d;
016import imagingbook.common.geometry.basic.PntUtils;
017import imagingbook.common.geometry.fitting.line.LineFit;
018import imagingbook.common.geometry.fitting.line.LinearRegressionFit;
019import imagingbook.common.geometry.fitting.line.OrthogonalLineFitEigen;
020import imagingbook.common.geometry.fitting.line.utils.LineSampler;
021import imagingbook.common.geometry.line.AlgebraicLine;
022import imagingbook.common.geometry.mappings.linear.AffineMapping2D;
023import imagingbook.common.geometry.mappings.linear.Rotation2D;
024import imagingbook.common.geometry.mappings.linear.Translation2D;
025import imagingbook.common.ij.overlay.ColoredStroke;
026import imagingbook.common.ij.overlay.ShapeOverlayAdapter;
027import imagingbook.core.jdoc.JavaDocHelp;
028
029import java.awt.Color;
030import java.util.ArrayList;
031import java.util.List;
032
033/**
034 * <p>
035 * ImageJ demo plugin, performs line fitting to a randomly sampled point set
036 * that is rotated in uniform steps. The result is shown as a stack of images
037 * with graphic overlays.
038 * </p>
039 * <p>
040 * Two fitting methods are employed: (a) linear regression fitting, (b)
041 * orthogonal regression fitting. The result of the first varies with rotation,
042 * while orthogonal fitting is rotation-invariant. See Sec. 10.2 (Fig. 10.4) of
043 * [1] for additional details.
044 * </p>
045 * <p>
046 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing &ndash; An Algorithmic
047 * Introduction</em>, 3rd ed, Springer (2022).
048 * </p>
049 * 
050 * @author WB
051 * @version 2022/09/30
052 * @see imagingbook.common.geometry.fitting.line.LinearRegressionFit
053 * @see imagingbook.common.geometry.fitting.line.OrthogonalLineFitEigen
054 */
055public class Line_Rotation_Demo implements PlugIn, JavaDocHelp {
056
057        public static int W = 400;
058        public static int H = 400;
059        
060        public static final double StrokeWidth = 1.0;
061        public static double DashLength = 10;
062        
063        public static final Color CentroidColor =               new Color(0,176,80);
064        public static final Color RegressionFitColor =  new Color(255,0,0);
065        public static final Color OrthogonalFitColor =  new Color(0,112,192);
066        public static final Color PointColor = Color.blue;
067        public static double PointRadius = 2;
068
069        public static Pnt2d P1 = Pnt2d.from(50, 0.5*H);
070        public static Pnt2d P2 = Pnt2d.from(W-50, 0.5*H);
071        public static int N = 100;
072        public static double Sigma = 15.0;
073
074        public static boolean DrawSamplePoints = true;
075        public static boolean DrawOrthogalFit = true;
076        public static boolean DrawRegressionFit = true;
077        public static boolean DrawCentroid = true;
078
079        @Override
080        public void run(String arg) {
081                Pnt2d[] pts0 = new LineSampler(P1, P2).getPoints(N, Sigma);     
082                Translation2D t1 = new Translation2D(-0.5 * W, -0.5 * H);
083                Translation2D t2 = t1.getInverse();
084                
085                List<ImagePlus> imageList = new ArrayList<>();
086                
087                ColoredStroke pointStroke = new ColoredStroke(StrokeWidth, PointColor, 0);
088                pointStroke.setFillColor(PointColor);
089                ColoredStroke strokeOrth = new ColoredStroke(StrokeWidth, OrthogonalFitColor, 0);
090                ColoredStroke strokeReg = new ColoredStroke(StrokeWidth, RegressionFitColor, DashLength);
091                ColoredStroke strokeCtr = new ColoredStroke(StrokeWidth, CentroidColor, 0);
092
093                // step-wise rotation about the image center (in degrees):
094                for (int theta = 0; theta < 120; theta += 20) {         
095                        Rotation2D rot = new Rotation2D(Math.toRadians(theta));
096                        AffineMapping2D map = t1.concat(rot).concat(t2);
097                        Pnt2d[] pts = map.applyTo(pts0);
098
099                        LineFit fitOrth = new OrthogonalLineFitEigen(pts);
100                        AlgebraicLine lineOrth = fitOrth.getLine();
101
102                        LineFit fitReg = new LinearRegressionFit(pts);
103                        AlgebraicLine lineReg = fitReg.getLine();
104
105                        ByteProcessor ip = new ByteProcessor(W, H);
106                        ip.setColor(255);
107                        ip.fill();
108
109                        ShapeOverlayAdapter ola = new ShapeOverlayAdapter();
110
111                        if (DrawSamplePoints) {                         
112                                for (Pnt2d p : pts) {
113                                        ola.addShape(p.getShape(PointRadius), pointStroke);
114                                }
115                        }
116
117                        if (DrawOrthogalFit) {
118                                ola.addShape(lineOrth.getShape(W, H), strokeOrth);
119                        }
120
121                        if (DrawRegressionFit) {
122                                ola.addShape(lineReg.getShape(W, H), strokeReg);
123                        }
124                        
125                        if (DrawCentroid) {
126                                Pnt2d ctr = PntUtils.centroid(pts);                     
127                                ola.addShape(ctr.getShape(PointRadius*2), strokeCtr);
128                        }
129
130                        String title = String.format("line-%03d", theta);
131                        ImagePlus im = new ImagePlus(title, ip);
132                        im.setOverlay(ola.getOverlay());
133                        imageList.add(im);
134                }
135                
136                ImagePlus stackIm = ImagesToStack.run(imageList.toArray(new ImagePlus[0]));
137                stackIm.setTitle(this.getClass().getSimpleName());
138                stackIm.show();
139        }
140
141}