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.image.pixel; 031 032import java.io.DataInput; 033import java.io.DataOutput; 034import java.io.IOException; 035import java.io.PrintWriter; 036import java.util.HashSet; 037import java.util.Iterator; 038import java.util.Scanner; 039import java.util.Set; 040 041import org.openimaj.image.FImage; 042import org.openimaj.image.Image; 043import org.openimaj.image.MBFImage; 044import org.openimaj.io.ReadWriteable; 045import org.openimaj.math.geometry.shape.Rectangle; 046import org.openimaj.math.geometry.shape.Shape; 047import org.openimaj.math.matrix.MatrixUtils; 048 049import Jama.Matrix; 050 051/** 052 * A set of (not-necessarily connected) pixels within an image. This class 053 * provides a number of utility functions for working with and analysing the 054 * pixels. 055 * 056 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 057 * 058 */ 059public class PixelSet implements Cloneable, ReadWriteable, Iterable<Pixel> { 060 061 /** The set of pixels within this connected component */ 062 public Set<Pixel> pixels = new HashSet<Pixel>(); 063 064 /** 065 * Default constructor; makes an empty PixelSet 066 */ 067 public PixelSet() { 068 // intentionally left blank 069 } 070 071 /** 072 * Construct a rectangular {@link PixelSet}. Pixels are created for the area 073 * within the rectangle but these will not have any specific value as they 074 * are not associated to any particular image. 075 * 076 * @param x 077 * The top-left x-coordinate of the rectangle 078 * @param y 079 * The top-left y-coordinate of the rectangle 080 * @param w 081 * The width of the rectangle 082 * @param h 083 * The height of the rectangle 084 */ 085 public PixelSet(int x, int y, int w, int h) { 086 for (int j = y; j < h + y; j++) 087 for (int i = x; i < w + x; i++) 088 pixels.add(new Pixel(i, j)); 089 } 090 091 /** 092 * Constructs a PixelSet from a mask image. Pixels are created for areas 093 * within the mask image that are non-zero. Note that this may result in a 094 * connected component definition that is unconnected and some methods may 095 * not return expected results. 096 * 097 * @param img 098 * The mask image to construct a connected component from. 099 */ 100 public PixelSet(int[][] img) { 101 for (int j = 0; j < img.length; j++) { 102 for (int i = 0; i < img[j].length; i++) { 103 if (img[j][i] > 0) 104 pixels.add(new Pixel(i, j)); 105 } 106 } 107 } 108 109 /** 110 * Constructs a PixelSet from a mask image. Pixels are created for areas 111 * within the mask image that are above the given threshold. Note that this 112 * may result in a connected component definition that is unconnected and 113 * some methods may not return expected results. 114 * 115 * @param mask 116 * The mask image to construct a connected component from. 117 * @param thresh 118 * the threshold value. 119 */ 120 public PixelSet(FImage mask, float thresh) { 121 for (int j = 0; j < mask.height; j++) { 122 for (int i = 0; i < mask.width; i++) { 123 if (mask.pixels[j][i] >= thresh) 124 pixels.add(new Pixel(i, j)); 125 } 126 } 127 } 128 129 /** 130 * Construct a PixelSet from the given set of {@link Pixel}s. The pixels are 131 * shallow copied into the connected component. If the pixels do not form a 132 * connected component then some methods in this class may not return 133 * expected results. 134 * 135 * @param pixels 136 * A {@link Set} of {@link Pixel}s. 137 */ 138 public PixelSet(Set<Pixel> pixels) { 139 this.pixels.addAll(pixels); 140 } 141 142 protected void fromShape(Shape shape) { 143 final int minx = (int) Math.round(shape.minX()); 144 final int maxx = (int) Math.round(shape.maxX()); 145 final int miny = (int) Math.round(shape.minY()); 146 final int maxy = (int) Math.round(shape.maxY()); 147 148 for (int y = miny; y <= maxy; y++) { 149 for (int x = minx; x <= maxx; x++) { 150 final Pixel p = new Pixel(x, y); 151 if (shape.isInside(p)) 152 addPixel(p); 153 } 154 } 155 } 156 157 /** 158 * Add a pixel into this connected component. If the pixel is not adjacent 159 * to another pixel within this connected component, then some methods may 160 * return unexpected results. Side-affects this object. 161 * 162 * @param x 163 * The x-coordinate of the pixel to add 164 * @param y 165 * The y-coordinate of the pixel to add 166 */ 167 public void addPixel(int x, int y) { 168 addPixel(new Pixel(x, y)); 169 } 170 171 /** 172 * Add a pixel into this connected component. If the pixel is not adjacent 173 * to another pixel within this connected component, then some methods may 174 * return unexpected results. Side-affects this object. 175 * 176 * @param p 177 * The pixel to add 178 */ 179 public void addPixel(Pixel p) { 180 pixels.add(p); 181 } 182 183 /** 184 * Returns the set of {@link Pixel}s that are within this component. 185 * 186 * @return the set of {@link Pixel}s that are within this component. 187 */ 188 public Set<Pixel> getPixels() { 189 return pixels; 190 } 191 192 /** 193 * Shallow copies the pixels from the given {@link ConnectedComponent} into 194 * this object. If the pixels that are added are not adjacent to other 195 * pixels within the component some methods may return unexpected results. 196 * Side-affects this object. 197 * 198 * @param c 199 * The {@link ConnectedComponent} to copy pixels from. 200 */ 201 public void merge(ConnectedComponent c) { 202 pixels.addAll(c.pixels); 203 } 204 205 /** 206 * Returns whether the given pixel is within this connected component. This 207 * is synonymous to 208 * <code>{@link #getPixels() getPixels}().contains(p)</code>. 209 * 210 * @param p 211 * The pixel to find. 212 * @return TRUE if the pixel is contained within this component; FALSE 213 * otherwise 214 */ 215 public boolean find(Pixel p) { 216 return pixels.contains(p); 217 } 218 219 /** 220 * Returns whether the given coordinates are within this connected 221 * component. This is synonymous with 222 * <code>{@link #find(Pixel) find}( new Pixel(x,y) )</code>. 223 * 224 * @param x 225 * The x-coordinate of the pixel to find 226 * @param y 227 * The y-coordinate of the pixel to find 228 * @return TRUE if the pixel is contained within this component; FALSE 229 * otherwise 230 */ 231 public boolean find(int x, int y) { 232 return find(new Pixel(x, y)); 233 } 234 235 /** 236 * Calculate the area of this connected component in pixels. This is 237 * synonymous with <code>{@link #getPixels() getPixels}.size()</code> 238 * 239 * @return the number of pixels covered by this connected component. 240 */ 241 public int calculateArea() { 242 return pixels.size(); 243 } 244 245 /** 246 * Calculate the pq moment, μ<sub>pq</sub> around the given centroid. From 247 * Equation 6.44 in Sonka, Hlavac and Boyle. If instead of giving the 248 * centroid (xc, yc), you give (0,0), then this will return the standard 249 * moments m<sub>pq</sub> about the origin. 250 * 251 * @param p 252 * The P moment to calculate 253 * @param q 254 * The Q moment to calculate 255 * @param xc 256 * x-coordinate of centroid 257 * @param yc 258 * y-coordinate of centroid 259 * @return The pq moment, M<sub>pq</sub>. 260 */ 261 public double calculateMoment(int p, int q, double xc, double yc) { 262 if (p == 0 && q == 0) 263 return calculateArea(); 264 265 double mpq = 0; 266 for (final Pixel pix : pixels) { 267 mpq += Math.pow(pix.x - xc, p) * Math.pow(pix.y - yc, q); 268 } 269 return mpq; 270 } 271 272 /** 273 * Calculate the pq central moment, μ<sub>pq</sub> for this region. From 274 * Equation 6.44 in Sonka, Hlavac and Boyle. 275 * 276 * @param p 277 * The P moment to calculate 278 * @param q 279 * The Q moment to calculate 280 * @return The pq moment, M<sub>pq</sub>. 281 */ 282 public double calculateMoment(int p, int q) { 283 if (p == 0 && q == 0) 284 return calculateArea(); 285 286 final double[] centroid = calculateCentroid(); 287 288 double mpq = 0; 289 for (final Pixel pix : pixels) { 290 mpq += Math.pow(pix.x - centroid[0], p) * Math.pow(pix.y - centroid[1], q); 291 } 292 return mpq; 293 } 294 295 /** 296 * Calculate the normalized, unscaled, central moments η<sub>pq</sub>. From 297 * Equation 6.47 in Sonka, Hlavac and Boyle [1st Ed.]. Normalised central 298 * moments are invariant to scale and translation. 299 * 300 * @param p 301 * The P moment to calculate 302 * @param q 303 * The Q moment to calculate 304 * @return The normalised, unscaled central moment, M<sub>pq</sub>. 305 */ 306 public double calculateMomentNormalised(int p, int q) { 307 final double gamma = ((p + q) / 2) + 1; 308 return calculateMoment(p, q) / Math.pow(pixels.size(), gamma); 309 } 310 311 /** 312 * Calculates the principle direction of the connected component. This is 313 * given by 314 * <code>0.5 * atan( (M<sub>20</sub>-M<sub>02</sub>) / 2 * M<sub>11</sub> )</code> 315 * so results in an angle between -PI and +PI. 316 * 317 * @return The principle direction (-PI/2 to +PI/2 radians) of the connected 318 * component. 319 */ 320 public double calculateDirection() { 321 final double[] centroid = calculateCentroid(); 322 final double u11 = calculateMoment(1, 1, centroid[0], centroid[1]); 323 final double u20 = calculateMoment(2, 0, centroid[0], centroid[1]); 324 final double u02 = calculateMoment(0, 2, centroid[0], centroid[1]); 325 326 final double theta = 0.5 * Math.atan2((2 * u11), (u20 - u02)); 327 328 return theta; 329 } 330 331 /** 332 * Calculate the centroid of the connected component. This is the average of 333 * all the pixel coordinates in the component. The result is returned in a 334 * double array where the the first index is the x-coordinate and second is 335 * the y-coordinate. 336 * 337 * @return The centroid point as a double array (x then y). 338 */ 339 public double[] calculateCentroid() { 340 final double[] centroid = new double[2]; 341 final double m00 = calculateMoment(0, 0, 0, 0); 342 centroid[0] = calculateMoment(1, 0, 0, 0) / m00; 343 centroid[1] = calculateMoment(0, 1, 0, 0) / m00; 344 345 return centroid; 346 } 347 348 /** 349 * Calculates the centroid pixel of the connected component. That is, the 350 * centroid value is rounded to the nearest pixel. 351 * 352 * @return A {@link Pixel} at the centroid. 353 */ 354 public Pixel calculateCentroidPixel() { 355 final double[] centroid = calculateCentroid(); 356 return new Pixel((int) Math.round(centroid[0]), (int) Math.round(centroid[1])); 357 } 358 359 /** 360 * Calculate the height and width of a box surrounding the component by 361 * averaging the distances of pixels above and below the centroid. The 362 * result is a double array where the first index is the height and the 363 * second is the width. 364 * 365 * @param centroid 366 * The centroid of the component. 367 * @return average height and width as a double array. 368 */ 369 public double[] calculateAverageHeightWidth(double[] centroid) { 370 double height, width, accumPosH = 0, accumNegH = 0, accumPosW = 0, accumNegW = 0; 371 int nPosH = 0, nNegH = 0, nPosW = 0, nNegW = 0; 372 373 for (final Pixel p : pixels) { 374 final double x = p.getX() - centroid[0]; 375 final double y = p.getY() - centroid[1]; 376 377 if (x >= 0) { 378 accumPosW += x; 379 nPosW++; 380 } else { 381 accumNegW += x; 382 nNegW++; 383 } 384 385 if (y >= 0) { 386 accumPosH += y; 387 nPosH++; 388 } else { 389 accumNegH += y; 390 nNegH++; 391 } 392 } 393 height = 2 * ((accumPosH / nPosH) + Math.abs(accumNegH / nNegH)); 394 width = 2 * ((accumPosW / nPosW) + Math.abs(accumNegW / nNegW)); 395 396 return new double[] { height, width }; 397 } 398 399 /** 400 * Calculate the height and width of a box surrounding the component by 401 * averaging the distances of pixels above and below the centroid. The 402 * result is a double array where the first index is the height and the 403 * second is the width. 404 * 405 * @return average height and width as a double array. 406 */ 407 public double[] calculateAverageHeightWidth() { 408 return calculateAverageHeightWidth(calculateCentroid()); 409 } 410 411 /** 412 * Compute the aspect ratio of the regular bounding box. 413 * 414 * @return the aspect ratio of the regular bounding box. 415 */ 416 public double calculateRegularBoundingBoxAspectRatio() { 417 final Rectangle bb = calculateRegularBoundingBox(); 418 419 return bb.width / bb.height; 420 } 421 422 /** 423 * Calculate the regular bounding box of the region by calculating the 424 * maximum and minimum x and y coordinates of the pixels contained within 425 * the region. The result is an integer array containing the (x,y) 426 * coordinate of the top-left of the bounding box, and the width and height 427 * of the bounding box. 428 * 429 * @return an {@link Rectangle} describing the bounds 430 */ 431 public Rectangle calculateRegularBoundingBox() { 432 int xmin = Integer.MAX_VALUE, xmax = 0, ymin = Integer.MAX_VALUE, ymax = 0; 433 434 for (final Pixel p : pixels) { 435 if (p.x < xmin) 436 xmin = p.x; 437 if (p.x > xmax) 438 xmax = p.x; 439 if (p.y < ymin) 440 ymin = p.y; 441 if (p.y > ymax) 442 ymax = p.y; 443 } 444 445 return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin); 446 } 447 448 /** 449 * Translates the region's pixels by x and y. This method side-affects the 450 * pixels in this object. 451 * 452 * @param x 453 * The offset in the horizontal direction 454 * @param y 455 * The offset in the vertical direction. 456 */ 457 public void translate(int x, int y) { 458 // Note: changing the position changes the hashcode, so you need to 459 // rehash the set! 460 final Set<Pixel> newPixels = new HashSet<Pixel>(); 461 462 for (final Pixel p : pixels) { 463 p.x += x; 464 p.y += y; 465 newPixels.add(p); 466 } 467 468 pixels = newPixels; 469 } 470 471 /** 472 * Gets the top-left most pixel within the connected component. 473 * 474 * @return the top-left most pixel within the connected component. 475 */ 476 public Pixel topLeftMostPixel() { 477 int top = Integer.MAX_VALUE; 478 Pixel pix = null; 479 for (final Pixel p : pixels) { 480 if (p.y < top) { 481 top = p.y; 482 pix = p; 483 } 484 } 485 486 for (final Pixel p : pixels) { 487 if (p.y == top) { 488 if (p.x < pix.x) 489 pix = p; 490 } 491 } 492 493 return pix; 494 } 495 496 /** 497 * Gets the bottom-right most pixel in the connected component. 498 * 499 * @return the bottom-right most pixel in the connected component. 500 */ 501 public Pixel bottomRightMostPixel() { 502 int bottom = Integer.MIN_VALUE; 503 Pixel pix = null; 504 for (final Pixel p : pixels) { 505 if (p.y > bottom) { 506 bottom = p.y; 507 pix = p; 508 } 509 } 510 511 for (final Pixel p : pixels) { 512 if (p.y == bottom) { 513 if (p.x > pix.x) 514 pix = p; 515 } 516 } 517 518 return pix; 519 } 520 521 /** 522 * {@inheritDoc} 523 * 524 * @see java.lang.Object#toString() 525 */ 526 @Override 527 public String toString() { 528 return "ConnectedComponent(" + "area=" + calculateArea() + ")"; 529 } 530 531 /** 532 * Extract a 1xarea image with all the pixels from the region in it. Useful 533 * for analysing the colour for example 534 * 535 * @param input 536 * input image to extract samples from 537 * @return new image with pixels set from samples 538 */ 539 public MBFImage extractPixels1d(MBFImage input) { 540 final MBFImage out = new MBFImage(pixels.size(), 1, input.numBands()); 541 542 int j = 0; 543 for (final Pixel p : pixels) { 544 for (int i = 0; i < input.numBands(); i++) { 545 out.setPixel(j, 0, input.getPixel(p.x, p.y)); 546 } 547 j++; 548 } 549 550 return out; 551 } 552 553 /** 554 * Extract a 1 x area image with all the pixels from the region in it. 555 * Useful for analysing the colour for example 556 * 557 * @param input 558 * image to extract pixel values from 559 * @return new image filled with pixel values 560 */ 561 public FImage extractPixels1d(FImage input) { 562 final FImage out = new FImage(pixels.size(), 1); 563 564 int j = 0; 565 for (final Pixel p : pixels) { 566 out.pixels[0][j] = input.pixels[p.y][p.x]; 567 j++; 568 } 569 570 return out; 571 } 572 573 /** 574 * Returns an image containing just the connected component cropped from the 575 * original image. Although that the result image is necessarily 576 * rectangular, the pixels which are not part of the connected component 577 * will be transparent. 578 * 579 * @param input 580 * The input image from which to take the pixels 581 * @param blackout 582 * Whether to blackout pixels that are not part of the region or 583 * whether to mark them as transparent 584 * @return An image with the component's pixels cropped 585 */ 586 public MBFImage crop(MBFImage input, boolean blackout) { 587 final Rectangle bb = this.calculateRegularBoundingBox(); 588 589 final MBFImage output = new MBFImage((int) bb.width, (int) bb.height, input.numBands()); 590 591 for (int y = 0; y < (int) bb.height; y++) { 592 for (int x = 0; x < (int) bb.width; x++) { 593 for (int b = 0; b < input.numBands(); b++) { 594 if (!blackout || this.pixels.contains(new Pixel(x + (int) bb.x, y + (int) bb.y))) 595 output.getBand(b).setPixel(x, y, input.getBand(b).getPixel(x + (int) bb.x, y + (int) bb.y)); 596 else 597 output.getBand(b).setPixel(x, y, 0f); 598 } 599 } 600 } 601 602 return output; 603 } 604 605 /** 606 * This is a convenience method that simply calls 607 * {@link #crop(MBFImage, boolean)} 608 * 609 * @param input 610 * The input image from which to take the pixels 611 * @param blackout 612 * Whether to blackout pixels that are not part of the region or 613 * whether to mark them as transparent 614 * @return An image with the component's pixels cropped 615 */ 616 public MBFImage extractPixels2d(MBFImage input, boolean blackout) { 617 return this.crop(input, blackout); 618 } 619 620 /** 621 * Returns an image where the connected component is masked in the image. 622 * The image is the same size as the image that is passed in. 623 * 624 * @param input 625 * The input image from which to take the size. 626 * @return An {@link FImage} containing a binary mask; pixels within the 627 * connected component will have value 1, outside with have value 0 628 */ 629 public FImage calculateBinaryMask(Image<?, ?> input) { 630 final FImage n = new FImage(input.getWidth(), input.getHeight()); 631 632 for (final Pixel p : pixels) 633 n.pixels[p.y][p.x] = 1; 634 635 return n; 636 } 637 638 /** 639 * Returns an ASCII representation of the connected component as a mask; 640 * where the output is "1" for a pixel within the mask and "0" for a pixel 641 * outside of the mask. 642 * 643 * @return An image string. 644 */ 645 public String toStringImage() { 646 final Rectangle bb = this.calculateRegularBoundingBox(); 647 648 String s = ""; 649 for (int j = (int) bb.y - 1; j <= bb.y + bb.height + 1; j++) { 650 for (int i = (int) bb.x - 1; i <= bb.x + bb.width + 1; i++) { 651 if (pixels.contains(new Pixel(i, j))) 652 s += "1"; 653 else 654 s += "0"; 655 } 656 s += "\n"; 657 } 658 return s; 659 } 660 661 /** 662 * Repositions the connected component so that its bounding box has its 663 * origin at (0,0). Side-affects this connected component. 664 */ 665 public void reposition() { 666 final Rectangle bb = this.calculateRegularBoundingBox(); 667 translate(-(int) bb.x, -(int) bb.y); 668 } 669 670 /** 671 * Returns a mask image for this connected component that will be the size 672 * of this component's bounding box. Pixels within the component will be set 673 * to value 1.0, while pixels outside of the component will retain their 674 * initial value. 675 * 676 * @return An {@link FImage} mask image 677 */ 678 public FImage toFImage() { 679 final Rectangle bb = this.calculateRegularBoundingBox(); 680 681 final FImage img = new FImage((int) (bb.x + bb.width + 1), (int) (bb.y + bb.height + 1)); 682 683 for (final Pixel p : pixels) 684 img.pixels[p.y][p.x] = 1; 685 686 return img; 687 } 688 689 /** 690 * Returns a mask image for this connected component that will be the size 691 * of this component's bounding box plus a border of the given amount of 692 * padding. Pixels within the component will be set to value 1.0, while 693 * pixels outside of the component will retain their initial value. 694 * 695 * @param padding 696 * The number of pixels padding to add around the outside of the 697 * mask. 698 * @return An {@link FImage} mask image 699 */ 700 public FImage toFImage(int padding) { 701 final Rectangle bb = this.calculateRegularBoundingBox(); 702 703 final FImage img = new FImage((int) (bb.x + bb.width + 1 + 2 * padding), 704 (int) (bb.y + bb.height + 1 + 2 * padding)); 705 706 for (final Pixel p : pixels) 707 img.pixels[p.y + padding][p.x + padding] = 1; 708 709 return img; 710 } 711 712 /** 713 * Affine transform the shape with the given transform matrix. Side-affects 714 * this component. 715 * 716 * @param transform 717 * The matrix containing the transform. 718 */ 719 public void transform(Matrix transform) { 720 final Matrix p1 = new Matrix(3, 1); 721 722 for (final Pixel p : pixels) { 723 p1.set(0, 0, p.getX()); 724 p1.set(1, 0, p.getY()); 725 p1.set(2, 0, 1); 726 727 final Matrix p2_est = transform.times(p1); 728 729 p.x = (int) Math.rint(p2_est.get(0, 0)); 730 p.y = (int) Math.rint(p2_est.get(1, 0)); 731 } 732 } 733 734 /** 735 * Returns a normalisation matrix for this component. 736 * 737 * @return A normalisation matrix. 738 */ 739 public Matrix normMatrix() { 740 final double u20 = calculateMoment(2, 0); 741 final double u02 = calculateMoment(0, 2); 742 final double u11 = -calculateMoment(1, 1); 743 744 Matrix tf = new Matrix(3, 3); 745 tf.set(0, 0, u20); 746 tf.set(1, 1, u02); 747 tf.set(0, 1, u11); 748 tf.set(1, 0, u11); 749 tf.set(2, 2, 1); 750 751 tf = tf.inverse(); 752 tf = tf.times(1 / Math.sqrt(tf.det())); 753 tf = MatrixUtils.sqrt(tf); 754 755 tf.set(2, 2, 1); 756 757 return tf; 758 } 759 760 @Override 761 public void readASCII(Scanner in) throws IOException { 762 final int count = in.nextInt(); 763 764 for (int i = 0; i < count; i++) { 765 final Pixel p = new Pixel(); 766 p.readASCII(in); 767 pixels.add(p); 768 } 769 } 770 771 @Override 772 public void readBinary(DataInput in) throws IOException { 773 final int count = in.readInt(); 774 775 for (int i = 0; i < count; i++) { 776 final Pixel p = new Pixel(); 777 p.readBinary(in); 778 pixels.add(p); 779 } 780 } 781 782 @Override 783 public String asciiHeader() { 784 return "PixelSet"; 785 } 786 787 @Override 788 public byte[] binaryHeader() { 789 return "PS".getBytes(); 790 } 791 792 @Override 793 public void writeASCII(PrintWriter out) throws IOException { 794 out.println(pixels.size()); 795 for (final Pixel p : pixels) { 796 p.writeASCII(out); 797 out.println(); 798 } 799 } 800 801 @Override 802 public void writeBinary(DataOutput out) throws IOException { 803 out.writeInt(pixels.size()); 804 for (final Pixel p : pixels) 805 p.writeBinary(out); 806 } 807 808 @Override 809 public Iterator<Pixel> iterator() { 810 return this.pixels.iterator(); 811 } 812 813}