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}