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-2016 Wilhelm Burger, Mark J. Burge. All rights reserved. 
007 * Visit http://imagingbook.com for additional details.
008 *******************************************************************************/
009package imagingbook.pub.fd;
010
011import static imagingbook.lib.math.Arithmetic.EPSILON_DOUBLE;
012import static imagingbook.lib.math.Arithmetic.sqr;
013import static java.lang.Math.PI;
014import static java.lang.Math.abs;
015import static java.lang.Math.cos;
016import static java.lang.Math.sin;
017import static java.lang.Math.sqrt;
018import ij.gui.Roi;
019import imagingbook.lib.math.Complex;
020
021import java.awt.Point;
022import java.awt.Polygon;
023import java.awt.geom.Point2D;
024import java.util.ArrayList;
025import java.util.List;
026
027
028/**
029 * Subclass of {@link FourierDescriptor} whose constructors assume
030 * that input polygons are non-uniformly sampled.
031 * 
032 * @author W. Burger
033 * @version 2015/08/13
034 */
035public class FourierDescriptorFromPolygon extends FourierDescriptor {
036
037        /**
038         * 
039         * @param V sequences of 2D points describing an arbitrary, closed polygon.
040         * @param Mp the number of Fourier coefficient pairs (M = 2 * Mp + 1).
041         */
042        public FourierDescriptorFromPolygon(Point2D[] V, int Mp) {
043                g = makeComplex(V);
044                makeDftSpectrumTrigonometric(Mp);
045        }
046        
047        /**
048         * 
049         * @param roi: a region of interest (ImageJ), not necessarily a polyline.
050         * @param Mp:  the number of Fourier coefficient pairs (M = 2 * Mp + 1)
051         */
052        public FourierDescriptorFromPolygon(Roi roi, int Mp) {
053                this(getRoiPoints(roi), Mp);
054        }
055        
056        void makeDftSpectrumTrigonometric(int Mp) {
057                final int N = g.length;                         // number of polygon vertices
058                final int M = 2 * Mp + 1;                       // number of Fourier coefficients
059        double[] dx = new double[N];            // dx[k] is the delta-x for polygon segment <k,k+1>
060        double[] dy = new double[N];            // dy[k] is the delta-y for polygon segment <k,k+1>
061        double[] lambda = new double[N];        // lambda[k] is the length of the polygon segment <k,k+1>
062        double[] L  = new double[N + 1];        // T[k] is the cumulated path length at polygon vertex k in [0,K]
063        
064        G = new Complex[M];
065        
066        L[0] = 0;
067        for (int i = 0; i < N; i++) {   // compute Dx, Dy, Dt and t tables
068            dx[i] = g[(i + 1) % N].re() - g[i].re();
069            dy[i] = g[(i + 1) % N].im() - g[i].im();
070            lambda[i] = sqrt(sqr(dx[i]) + sqr(dy[i])); 
071            if (abs(lambda[i]) < EPSILON_DOUBLE) {
072                        throw new Error("Zero-length polygon segment!");
073                }
074            L[i+1] = L[i] + lambda[i];
075        }
076        
077        double Ln = L[N];       // Ln is the closed polygon length
078               
079        // calculate DFT coefficient G[0]:
080        double x0 = g[0].re(); // V[0].getX();
081        double y0 = g[0].im(); // V[0].getY();
082        double a0 = 0;
083        double c0 = 0;
084        for (int i = 0; i < N; i++) {   // for each polygon vertex
085                double s = (sqr(L[i+1]) - sqr(L[i])) / (2 * lambda[i]) - L[i];
086                double xi = g[i].re(); // V[i].getX();
087                double yi = g[i].im(); // V[i].getY();
088                a0 = a0 + s * dx[i] + (xi - x0) * lambda[i];
089                c0 = c0 + s * dy[i] + (yi - y0) * lambda[i];
090        }
091        //G[0] = new Complex(x0 + a0/Ln, y0 + c0/Ln);
092        this.setCoefficient(0, new Complex(x0 + a0/Ln, y0 + c0/Ln));
093        
094        // calculate remaining FD pairs G[-m], G[+m] for m = 1,...,Mp
095        for (int m = 1; m <= Mp; m++) { // for each FD pair
096                double w = 2 * PI * m / Ln;
097                double a = 0, c = 0;
098                double b = 0, d = 0;
099            for (int i = 0; i < N; i++) {       //      for each polygon vertex
100                double w0 = w * L[i];                           
101                double w1 = w * L[(i + 1) % N];         
102                double dCos = cos(w1) - cos(w0);
103                a = a + dCos * (dx[i] / lambda[i]);
104                c = c + dCos * (dy[i] / lambda[i]);
105                double dSin = sin(w1) - sin(w0);
106                b = b + dSin * (dx[i] / lambda[i]);
107                d = d + dSin * (dy[i] / lambda[i]);
108            }
109            double s = Ln / sqr(2 * PI * m);
110            this.setCoefficient(+m, new Complex(s * (a + d), s * (c - b)));
111            this.setCoefficient(-m, new Complex(s * (a - d), s * (b + c)));
112        }
113        }
114        
115        static Point2D[] getRoiPoints(Roi roi) {
116                Polygon poly = roi.getPolygon();
117                int[] xp = poly.xpoints;
118                int[] yp = poly.ypoints;
119                // copy vertices for all non-zero-length polygon segments:
120                List<Point> points = new ArrayList<Point>(xp.length);
121                points.add(new Point(xp[0], yp[0]));
122                int last = 0;
123                for (int i = 1; i < xp.length; i++) {
124                        if (xp[last] != xp[i] || yp[last] != yp[i]) {
125                                points.add(new Point(xp[i], yp[i]));
126                                last = i;
127                        }
128                }
129                // remove last point if the closing segment has zero length:
130                if (xp[last] == xp[0] && yp[last] == yp[0]) {
131                        points.remove(last);
132                }
133                return points.toArray(new Point2D[0]);
134        }
135}