001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.math.geometry.shape; 031 032import java.util.ArrayList; 033import java.util.Collection; 034import java.util.Iterator; 035import java.util.List; 036 037import org.openimaj.math.geometry.GeometricObject; 038import org.openimaj.math.geometry.line.Line2d; 039import org.openimaj.math.geometry.point.Point2d; 040import org.openimaj.math.geometry.point.Point2dImpl; 041import org.openimaj.math.geometry.shape.util.GrahamScan; 042 043import Jama.Matrix; 044 045/** 046 * A base implementation of a {@link GeometricObject} that is a set of points in 047 * space. 048 * 049 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 050 * 051 */ 052public class PointList implements GeometricObject, Iterable<Point2d>, Cloneable { 053 /** The points in the {@link PointList} */ 054 public List<Point2d> points = new ArrayList<Point2d>(); 055 056 /** 057 * Construct a {@link PointList} from points 058 * 059 * @param points 060 * the points 061 */ 062 public PointList(Point2d... points) { 063 for (final Point2d v : points) 064 this.points.add(v); 065 } 066 067 /** 068 * Construct a {@link PointList} from points 069 * 070 * @param points 071 * the points 072 */ 073 public PointList(Collection<? extends Point2d> points) { 074 this(points, false); 075 } 076 077 /** 078 * Construct a {@link PointList} from the points, possibly copying the 079 * points first 080 * 081 * @param points 082 * the points 083 * @param copy 084 * should the points be copied 085 */ 086 public PointList(Collection<? extends Point2d> points, boolean copy) { 087 if (!copy) 088 this.points.addAll(points); 089 else 090 { 091 for (final Point2d p : points) 092 this.points.add(new Point2dImpl(p.getX(), p.getY())); 093 } 094 } 095 096 void rotate(Point2d point, Point2d origin, double angle) { 097 final double X = origin.getX() + ((point.getX() - origin.getX()) * Math.cos(angle) - 098 (point.getY() - origin.getY()) * Math.sin(angle)); 099 100 final double Y = origin.getY() + ((point.getX() - origin.getX()) * Math.sin(angle) + 101 (point.getY() - origin.getY()) * Math.cos(angle)); 102 103 point.setX((float) X); 104 point.setY((float) Y); 105 } 106 107 /** 108 * Rotate the {@link PointList} about the given origin with the given angle 109 * (in radians) 110 * 111 * @param origin 112 * the origin of the rotation 113 * @param angle 114 * the angle in radians 115 */ 116 public void rotate(Point2d origin, double angle) { 117 for (final Point2d p : points) 118 rotate(p, origin, angle); 119 } 120 121 /** 122 * Rotate the {@link PointList} about (0,0) with the given angle (in 123 * radians) 124 * 125 * @param angle 126 * the angle in radians 127 */ 128 public void rotate(double angle) { 129 this.rotate(new Point2dImpl(0, 0), angle); 130 } 131 132 /** 133 * Compute the regular (oriented to the axes) bounding box of the 134 * {@link PointList}. 135 * 136 * @return the regular bounding box 137 */ 138 @Override 139 public Rectangle calculateRegularBoundingBox() { 140 int xmin = Integer.MAX_VALUE, xmax = 0, ymin = Integer.MAX_VALUE, ymax = 0; 141 142 for (final Point2d p : points) { 143 if (p.getX() < xmin) 144 xmin = (int) Math.floor(p.getX()); 145 if (p.getX() > xmax) 146 xmax = (int) Math.ceil(p.getX()); 147 if (p.getY() < ymin) 148 ymin = (int) Math.floor(p.getY()); 149 if (p.getY() > ymax) 150 ymax = (int) Math.ceil(p.getY()); 151 } 152 153 return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin); 154 } 155 156 /** 157 * Translate the {@link PointList}s position 158 * 159 * @param x 160 * x-translation 161 * @param y 162 * y-translation 163 */ 164 @Override 165 public void translate(float x, float y) { 166 for (final Point2d p : points) { 167 p.setX(p.getX() + x); 168 p.setY(p.getY() + y); 169 } 170 } 171 172 /** 173 * Scale the {@link PointList} by the given amount about (0,0). Scalefactors 174 * between 0 and 1 shrink the {@link PointList}. 175 * 176 * @param sc 177 * the scale factor. 178 */ 179 @Override 180 public void scale(float sc) { 181 for (final Point2d p : points) { 182 p.setX(p.getX() * sc); 183 p.setY(p.getY() * sc); 184 } 185 } 186 187 /** 188 * Scale the {@link PointList} only in the x-direction by the given amount 189 * about (0,0). Scale factors between 0 and 1 will shrink the 190 * {@link PointList} 191 * 192 * @param sc 193 * The scale factor 194 * @return this {@link PointList} 195 */ 196 public PointList scaleX(float sc) 197 { 198 for (final Point2d p : points) { 199 p.setX(p.getX() * sc); 200 } 201 return this; 202 } 203 204 /** 205 * Scale the {@link PointList} only in the y-direction by the given amount 206 * about (0,0). Scale factors between 0 and 1 will shrink the 207 * {@link PointList} 208 * 209 * @param sc 210 * The scale factor 211 * @return this {@link PointList} 212 */ 213 public PointList scaleY(float sc) 214 { 215 for (final Point2d p : points) { 216 p.setY(p.getY() * sc); 217 } 218 return this; 219 } 220 221 /** 222 * Scale the {@link PointList} by the given amount about (0,0). Scale 223 * factors between 0 and 1 shrink the {@link PointList}. 224 * 225 * @param scx 226 * the scale factor in the x direction 227 * @param scy 228 * the scale factor in the y direction. 229 * @return this {@link PointList} 230 */ 231 public PointList scaleXY(float scx, float scy) 232 { 233 for (final Point2d p : points) { 234 p.setX(p.getX() * scx); 235 p.setY(p.getY() * scy); 236 } 237 return this; 238 } 239 240 /** 241 * Scale the {@link PointList} by the given amount about the given point. 242 * Scalefactors between 0 and 1 shrink the {@link PointList}. 243 * 244 * @param centre 245 * the centre of the scaling operation 246 * @param sc 247 * the scale factor 248 */ 249 @Override 250 public void scale(Point2d centre, float sc) { 251 this.translate(-centre.getX(), -centre.getY()); 252 253 for (final Point2d p : points) { 254 p.setX(p.getX() * sc); 255 p.setY(p.getY() * sc); 256 } 257 258 this.translate(centre.getX(), centre.getY()); 259 } 260 261 /** 262 * Scale the {@link PointList} about its centre of gravity. Scalefactors 263 * between 0 and 1 shrink the {@link PointList}. 264 * 265 * @param sc 266 * the scale factor 267 */ 268 @Override 269 public void scaleCentroid(float sc) 270 { 271 final Point2d cog = calculateCentroid(); 272 this.translate(-cog.getX(), -cog.getY()); 273 this.scale(sc); 274 this.translate(cog.getX(), cog.getY()); 275 } 276 277 /** 278 * Get the centre of gravity of the {@link PointList} 279 * 280 * @return the centre of gravity of the {@link PointList} 281 */ 282 @Override 283 public Point2d calculateCentroid() 284 { 285 float xSum = 0; 286 float ySum = 0; 287 288 int n = 0; 289 290 for (final Point2d p : points) { 291 xSum += p.getX(); 292 ySum += p.getY(); 293 n++; 294 } 295 296 xSum /= n; 297 ySum /= n; 298 299 return new Point2dImpl(xSum, ySum); 300 } 301 302 /** 303 * @return the minimum x-ordinate of all vertices 304 */ 305 @Override 306 public double minX() { 307 return calculateRegularBoundingBox().x; 308 } 309 310 /** 311 * @return the minimum y-ordinate of all vertices 312 */ 313 @Override 314 public double minY() { 315 return calculateRegularBoundingBox().y; 316 } 317 318 /** 319 * @return the maximum x-ordinate of all vertices 320 */ 321 @Override 322 public double maxX() { 323 final Rectangle r = calculateRegularBoundingBox(); 324 return r.x + r.width; 325 } 326 327 /** 328 * @return the maximum y-ordinate of all vertices 329 */ 330 @Override 331 public double maxY() { 332 final Rectangle r = calculateRegularBoundingBox(); 333 return r.y + r.height; 334 } 335 336 /** 337 * @return the width of the regular bounding box 338 */ 339 @Override 340 public double getWidth() { 341 return maxX() - minX(); 342 } 343 344 /** 345 * @return the height of the regular bounding box 346 */ 347 @Override 348 public double getHeight() { 349 return maxY() - minY(); 350 } 351 352 /** 353 * Apply a 3x3 transform matrix to a copy of the {@link PointList} and 354 * return it 355 * 356 * @param transform 357 * 3x3 transform matrix 358 * @return the transformed {@link PointList} 359 */ 360 @Override 361 public PointList transform(Matrix transform) { 362 final List<Point2d> newVertices = new ArrayList<Point2d>(); 363 364 for (final Point2d p : points) { 365 final Matrix p1 = new Matrix(3, 1); 366 p1.set(0, 0, p.getX()); 367 p1.set(1, 0, p.getY()); 368 p1.set(2, 0, 1); 369 370 final Matrix p2_est = transform.times(p1); 371 372 final Point2d out = new Point2dImpl((float) (p2_est.get(0, 0) / p2_est.get(2, 0)), 373 (float) (p2_est.get(1, 0) / p2_est.get(2, 0))); 374 375 newVertices.add(out); 376 } 377 378 return new PointList(newVertices); 379 } 380 381 /** 382 * {@inheritDoc} 383 * 384 * @see java.lang.Iterable#iterator() 385 */ 386 @Override 387 public Iterator<Point2d> iterator() 388 { 389 return points.iterator(); 390 } 391 392 @Override 393 public String toString() { 394 return points.toString(); 395 } 396 397 /** 398 * Compute the mean of a set of {@link PointList}s. It is assumed that the 399 * number of points in the {@link PointList}s is equal, and that their is a 400 * one-to-one correspondance between the ith point in each list. 401 * 402 * @param shapes 403 * the shapes to average 404 * @return the average shape 405 */ 406 public static PointList computeMean(Collection<PointList> shapes) { 407 final int npoints = shapes.iterator().next().points.size(); 408 final PointList mean = new PointList(); 409 410 for (int i = 0; i < npoints; i++) 411 mean.points.add(new Point2dImpl()); 412 413 for (final PointList shape : shapes) { 414 for (int i = 0; i < npoints; i++) { 415 final Point2dImpl pt = (Point2dImpl) mean.points.get(i); 416 417 pt.x += shape.points.get(i).getX(); 418 pt.y += shape.points.get(i).getY(); 419 } 420 } 421 422 for (int i = 0; i < npoints; i++) { 423 final Point2dImpl pt = (Point2dImpl) mean.points.get(i); 424 425 pt.x /= shapes.size(); 426 pt.y /= shapes.size(); 427 } 428 429 return mean; 430 } 431 432 /** 433 * @return the number of points in the list 434 */ 435 public int size() { 436 return points.size(); 437 } 438 439 /** 440 * Calculate the intrinsic scale of the shape. This is the RMS distance of 441 * all the points from the centroid. 442 * 443 * @return the scale of the object. 444 */ 445 public float computeIntrinsicScale() { 446 final Point2d cog = this.calculateCentroid(); 447 float scale = 0; 448 449 for (final Point2d pt : this) { 450 final double x = pt.getX() - cog.getX(); 451 final double y = pt.getY() - cog.getY(); 452 453 scale += x * x + y * y; 454 } 455 456 return (float) Math.sqrt(scale / points.size()); 457 } 458 459 /** 460 * Get the ith point 461 * 462 * @param i 463 * the index of the point 464 * @return the ith point 465 */ 466 public Point2d get(int i) { 467 return points.get(i); 468 } 469 470 /** 471 * @return A list of {@link Line2d} assuming the points in this list are 472 * connected in order 473 */ 474 public List<Line2d> getLines() { 475 final List<Line2d> lines = new ArrayList<Line2d>(points.size() - 1); 476 477 for (int i = 1; i < this.points.size(); i++) { 478 lines.add(new Line2d( 479 points.get(i - 1), 480 points.get(i) 481 )); 482 } 483 484 return lines; 485 } 486 487 /** 488 * @param conns 489 * @return calls {@link PointListConnections#getLines(PointList)} with this 490 */ 491 public List<Line2d> getLines(PointListConnections conns) { 492 return conns.getLines(this); 493 } 494 495 @Override 496 public PointList clone() { 497 final PointList p = new PointList(); 498 for (final Point2d point2d : this) { 499 p.points.add(point2d.copy()); 500 } 501 return p; 502 } 503 504 /** 505 * Calculate the convex hull of the points using the Graham Scan algorithm. 506 * 507 * @see GrahamScan 508 * @return the convex hull 509 */ 510 public Polygon calculateConvexHull() { 511 return GrahamScan.getConvexHull(this.points); 512 } 513}