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}