/* * Copyright 2010, 2011 Institut Pasteur. * * This file is part of NHerve Main Toolbox, which is an ICY plugin. * * NHerve Main Toolbox is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NHerve Main Toolbox is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NHerve Main Toolbox. If not, see <http://www.gnu.org/licenses/>. */ package plugins.nherve.toolbox.image.feature.region; import java.awt.Point; import java.util.Vector; import plugins.nherve.toolbox.Algorithm; // http://www.cs.princeton.edu/~ah/alg_anim/version1/QuickHull.html /** * class that implements QuickHull algorithm to construct convex hull polygon<br> * usage example: <blockquote> * * <pre> * int nDots = 500; * int offset = 50; * int sizeX = 400; * int sizeY = 400; * Point[] dots = new Point[nDots]; * for (int i = 0; i < dots.length; i++) { * int px = (int) Math.round(offset + (sizeX - 2 * offset) * Math.random()); * int py = (int) Math.round(offset + (sizeY - 2 * offset) * Math.random()); * dots[i] = new Point(px, py); * } * qh = new QuickHull(dots); * Point[] dots = qh.getOriginalPoints(); * Vector outPoints = qh.getHullPointsAsVector(); * </pre> * * </blockquote> * * @author Nicolas HERVE - nicolas.herve@pasteur.fr */ public class QuickHull { /** The original points. */ Point[] originalPoints; /** The full steps. */ int fullSteps = 0; /** The hull points. */ Vector hullPoints = new Vector(); /* * constructor for <code>QuickHull</code> class @param originalPoints * {@link Point}[] initial points */ /** * Instantiates a new quick hull. * * @param originalPoints * the original points */ public QuickHull(Point[] originalPoints) { this.originalPoints = originalPoints; qhull(originalPoints, 0, 0); reorderPoints(hullPoints); } /** * Returns original {@link Point} array. * * @return original {@link Point} array */ public Point[] getOriginalPoints() { return originalPoints; } /** * Returns convex hull points as {@link Vector}. * * @return convex hull points as {@link Vector}. */ public Vector getHullPointsAsVector() { return (Vector) hullPoints.clone(); } /** * Returns convex hull points as {@link Point}[]. * * @return convex hull points as {@link Point}[]. */ public Point[] getHullPointsAsArray() { if (hullPoints == null) return null; Point[] hulldots = new Point[hullPoints.size()]; for (int i = 0; i < hulldots.length; i++) { hulldots[i] = (Point) hullPoints.elementAt(i); } return hulldots; } /** * Reorder points. * * @param v * the v */ void reorderPoints(Vector v) { AngleWrapper[] angleWrappers = new AngleWrapper[v.size()]; double xc = 0; double yc = 0; for (int i = 0; i < v.size(); i++) { Point pt = (Point) v.elementAt(i); xc += pt.x; yc += pt.y; } xc /= v.size(); yc /= v.size(); for (int i = 0; i < angleWrappers.length; i++) { angleWrappers[i] = createAngleWrapper((Point) v.elementAt(i), xc, yc); } java.util.Arrays.sort(angleWrappers, new AngleComparator()); v.removeAllElements(); for (int i = 0; i < angleWrappers.length; i++) { v.add(angleWrappers[i].pt); } } /** * Qhull. * * @param dots0 * the dots0 * @param up * the up * @param step * the step */ void qhull(Object[] dots0, int up, int step) { fullSteps++; if (dots0 == null || dots0.length < 1 || step > 200) return; if (dots0.length < 2) { addHullPoint((Point) dots0[0]); return; } try { int leftIndex = 0; int rightIndex = 0; for (int i = 1; i < dots0.length; i++) { if (((Point) dots0[i]).x < ((Point) dots0[leftIndex]).x) { leftIndex = i; } if (((Point) dots0[i]).x > ((Point) dots0[rightIndex]).x) { rightIndex = i; } } Point leftPoint = (Point) dots0[leftIndex]; Point rightPoint = (Point) dots0[rightIndex]; addHullPoint(leftPoint); addHullPoint(rightPoint); if (dots0.length == 3) { int middlePoint = -1; for (int i = 0; i < dots0.length; i++) { if (i == leftIndex || i == rightIndex) continue; middlePoint = i; break; } addHullPoint((Point) dots0[middlePoint]); } else if (dots0.length > 3) { Vector vIn = new Vector(); Vector vOut = new Vector(); if (up >= 0) { int upIndex = selectPoints(dots0, leftPoint, rightPoint, true, vIn); if (upIndex >= 0 && vIn.size() > 0) { Point upPoint = (Point) vIn.elementAt(upIndex); vOut.removeAllElements(); selectPoints(vIn, leftPoint, upPoint, true, vOut); qhull(vOut.toArray(), 1, step + 1); vOut.removeAllElements(); selectPoints(vIn, upPoint, rightPoint, true, vOut); qhull(vOut.toArray(), 1, step + 1); } } if (up <= 0) { vIn.removeAllElements(); int downIndex = selectPoints(dots0, rightPoint, leftPoint, false, vIn); if (downIndex >= 0 && vIn.size() > 0) { Point downPoint = (Point) vIn.elementAt(downIndex); vOut.removeAllElements(); selectPoints(vIn, rightPoint, downPoint, false, vOut); qhull(vOut.toArray(), -1, step + 1); vOut.removeAllElements(); selectPoints(vIn, downPoint, leftPoint, false, vOut); qhull(vOut.toArray(), -1, step + 1); } } } } catch (Throwable t) { } } /** * Adds the hull point. * * @param pt * the pt */ void addHullPoint(Point pt) { if (!hullPoints.contains(pt)) hullPoints.add(pt); } /** * Select points. * * @param pIn * the in * @param pLeft * the left * @param pRight * the right * @param up * the up * @param vOut * the v out * @return the int */ static int selectPoints(Object[] pIn, Point pLeft, Point pRight, boolean up, Vector vOut) { int retValue = -1; if (pIn == null || vOut == null) return retValue; double k = (double) (pRight.y - pLeft.y) / (double) (pRight.x - pLeft.x); double A = -k; double B = 1; double C = k * pLeft.x - pLeft.y; double dup = 0; for (int i = 0; i < pIn.length; i++) { Point pt = (Point) pIn[i]; if (pt.equals(pLeft) || pt.equals(pRight)) continue; int px = pt.x; int py = pt.y; double y = pLeft.y + k * (px - pLeft.x); if ((!up && y < py) || (up && y > py)) { vOut.add(pt); double d = (A * px + B * py + C); if (d < 0) d = -d; if (d > dup) { dup = d; retValue = vOut.size() - 1; } } } vOut.add(pLeft); vOut.add(pRight); return retValue; } /** * Select points. * * @param vIn * the v in * @param pLeft * the left * @param pRight * the right * @param up * the up * @param vOut * the v out * @return the int */ static int selectPoints(Vector vIn, Point pLeft, Point pRight, boolean up, Vector vOut) { int retValue = -1; if (vIn == null || vOut == null) return retValue; double k = (double) (pRight.y - pLeft.y) / (double) (pRight.x - pLeft.x); double A = -k; double B = 1; double C = k * pLeft.x - pLeft.y; double dup = 0; for (int i = 0; i < vIn.size(); i++) { Point pt = (Point) vIn.elementAt(i); if (pt.equals(pLeft) || pt.equals(pRight)) continue; int px = pt.x; int py = pt.y; double y = pLeft.y + k * (px - pLeft.x); if ((!up && y < py) || (up && y > py)) { vOut.add(pt); double d = (A * px + B * py + C); if (d < 0) d = -d; if (d > dup) { dup = d; retValue = vOut.size() - 1; } } } vOut.add(pLeft); vOut.add(pRight); return retValue; } /** * Creates the angle wrapper. * * @param pt * the pt * @param xc * the xc * @param yc * the yc * @return the angle wrapper */ static AngleWrapper createAngleWrapper(Point pt, double xc, double yc) { double angle = Math.atan2(pt.y - yc, pt.x - xc); if (angle < 0) angle += 2 * Math.PI; return new AngleWrapper(angle, new Point(pt)); } /** * The Class AngleComparator. * * @author Nicolas HERVE - nicolas.herve@pasteur.fr */ static class AngleComparator implements java.util.Comparator { /* (non-Javadoc) * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ public int compare(Object obj1, Object obj2) { if (!(obj1 instanceof AngleWrapper) || !(obj2 instanceof AngleWrapper)) return 0; AngleWrapper ac1 = (AngleWrapper) obj1; AngleWrapper ac2 = (AngleWrapper) obj2; return (ac1.angle < ac2.angle) ? -1 : 1; } } /** * The Class AngleWrapper. * * @author Nicolas HERVE - nicolas.herve@pasteur.fr */ static class AngleWrapper implements Comparable { /** The angle. */ double angle; /** The pt. */ Point pt; /** * Instantiates a new angle wrapper. * * @param angle * the angle * @param pt * the pt */ AngleWrapper(double angle, Point pt) { this.angle = angle; this.pt = pt; } /* (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(Object obj) { if (!(obj instanceof AngleWrapper)) return 0; AngleWrapper ac = (AngleWrapper) obj; return (ac.angle < angle) ? -1 : 1; } } /** The n dots. */ static int nDots = 500; /** The offset. */ static int offset = 50; /** The size x. */ static int sizeX = 400; /** The size y. */ static int sizeY = 400; /** The r. */ static double r = (double) sizeX / 2 - offset; /** The xc. */ static double xc = (double) sizeX / 2; /** The yc. */ static double yc = (double) sizeY / 2; /** The qh. */ static QuickHull qh; /** * Inits the dots. */ static void initDots() { Point[] dots = new Point[nDots]; for (int i = 0; i < dots.length; i++) { int px = (int) Math.round(offset + (sizeX - 2 * offset) * Math.random()); int py = (int) Math.round(offset + (sizeY - 2 * offset) * Math.random()); // double angle = (double)i*Math.PI*2/(double)dots.length; // int px = (int)Math.round(xc + r*Math.cos(angle)); // int py = (int)Math.round(yc + r*Math.sin(angle)); dots[i] = new Point(px, py); } qh = new QuickHull(dots); Algorithm.out("hullPoints " + qh.hullPoints.size() + " fullSteps " + qh.fullSteps); } /** * Draw dots. * * @param g * the g */ static void drawDots(java.awt.Graphics g) { if (qh == null) return; g.setColor(java.awt.Color.gray); Point[] dots = qh.getOriginalPoints(); for (int i = 0; i < dots.length; i++) { Point pt = dots[i]; g.fillRect(pt.x, pt.y, 3, 3); } g.setColor(java.awt.Color.red); Vector outPoints = qh.getHullPointsAsVector(); for (int i = 0; i < outPoints.size(); i++) { Point pt = (Point) outPoints.elementAt(i); g.fillRect(pt.x, pt.y, 3, 3); if (i > 0) { Point ptPrev = (Point) outPoints.elementAt(i - 1); g.drawLine(ptPrev.x, ptPrev.y, pt.x, pt.y); } if (i == outPoints.size() - 1) { Point ptPrev = (Point) outPoints.elementAt(0); g.drawLine(ptPrev.x, ptPrev.y, pt.x, pt.y); } } } /** * The main method. * * @param args * the arguments */ public static void main(String args[]) { javax.swing.JFrame frame1 = new javax.swing.JFrame("test") { boolean wasInited = false; public void paint(java.awt.Graphics g) { super.paint(g); if (!wasInited) { initDots(); wasInited = true; } drawDots(g); } }; frame1.setSize(sizeX, sizeY); frame1.setVisible(true); } }