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;
031
032import java.util.ArrayList;
033import java.util.Arrays;
034import java.util.Iterator;
035import java.util.List;
036
037import org.openimaj.image.colour.ColourSpace;
038import org.openimaj.image.processor.SinglebandImageProcessor;
039import org.openimaj.image.processor.SinglebandKernelProcessor;
040import org.openimaj.image.processor.SinglebandPixelProcessor;
041import org.openimaj.math.geometry.shape.Rectangle;
042
043/**
044 * A base class for multi-band images.
045 * 
046 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
047 * 
048 * @param <T>
049 *            The pixel type
050 * @param <I>
051 *            The concrete subclass type
052 * @param <S>
053 *            The concrete subclass type of each band
054 */
055public abstract class MultiBandImage<T extends Comparable<T>, I extends MultiBandImage<T, I, S>, S extends SingleBandImage<T, S>>
056                extends
057                Image<T[], I>
058                implements
059                Iterable<S>,
060                SinglebandImageProcessor.Processable<T, S, I>,
061                SinglebandKernelProcessor.Processable<T, S, I>
062
063{
064        private static final long serialVersionUID = 1L;
065
066        /** The images for each band in a list */
067        public List<S> bands;
068
069        /** The colour-space of this image */
070        public ColourSpace colourSpace = ColourSpace.CUSTOM;
071
072        /**
073         * Default constructor for a multiband image.
074         */
075        public MultiBandImage() {
076                this.bands = new ArrayList<S>();
077        }
078
079        /**
080         * Default constructor for a multiband image.
081         * 
082         * @param colourSpace
083         *            the colour space
084         */
085        public MultiBandImage(final ColourSpace colourSpace) {
086                this();
087                this.colourSpace = colourSpace;
088        }
089
090        /**
091         * Construct a multiband image using each of the given images as the bands
092         * (in order).
093         * 
094         * @param colourSpace
095         *            the colour space
096         * @param images
097         *            A set of images to use as the bands in the image.
098         */
099        public MultiBandImage(final ColourSpace colourSpace, final S... images) {
100                this(colourSpace);
101
102                if (!ImageUtilities.checkSameSize(images)) {
103                        throw new IllegalArgumentException("images are not the same size");
104                }
105
106                this.bands.addAll(Arrays.asList(images));
107        }
108
109        /**
110         * {@inheritDoc}
111         * 
112         * @see org.openimaj.image.Image#abs()
113         */
114        @SuppressWarnings("unchecked")
115        @Override
116        public I abs() {
117                for (final S i : this.bands)
118                        i.abs();
119                return (I) this;
120        }
121
122        /**
123         * Add the given scalar to each pixel of each band and return result as a
124         * new image.
125         * 
126         * @param num
127         *            The value to add to each pixel in every band.
128         * @return A new image containing the result.
129         */
130        public I add(final T num) {
131                final I newImage = this.clone();
132                newImage.add(num);
133                return newImage;
134        }
135
136        /**
137         * Adds a new band image to the multiband image. The given image must be the
138         * same size as the images already in this image.
139         * 
140         * @param img
141         *            The image to add as a new band.
142         */
143        public void addBand(final S img) {
144                if (this.bands.size() > 0) {
145                        if (!ImageUtilities.checkSize(this.getHeight(), this.getWidth(), img)) {
146                                throw new IllegalArgumentException("images are not the same size");
147                        }
148                }
149                this.bands.add(img);
150        }
151
152        /**
153         * {@inheritDoc} The input image must be a {@link MultiBandImage} or a
154         * {@link SingleBandImage}.
155         * 
156         * @see org.openimaj.image.Image#addInplace(org.openimaj.image.Image)
157         * @throws UnsupportedOperationException
158         *             if the given image is neither a {@link MultiBandImage} nor a
159         *             {@link SingleBandImage}.
160         */
161        @Override
162        public I addInplace(final Image<?, ?> im) {
163                if (im instanceof MultiBandImage<?, ?, ?>) {
164                        return this.addInplace((MultiBandImage<?, ?, ?>) im);
165                } else if (im instanceof SingleBandImage<?, ?>) {
166                        return this.addInplace((SingleBandImage<?, ?>) im);
167                } else {
168                        throw new UnsupportedOperationException("Unsupported Type");
169                }
170        }
171
172        /**
173         * Adds to each pixel the value of the corresponding pixel in the
174         * corresponding band in the given image. Side-affects this image.
175         * 
176         * @param im
177         *            The image to add to this image.
178         * @return A reference to this image containing the result.
179         */
180        @SuppressWarnings("unchecked")
181        public I addInplace(final MultiBandImage<?, ?, ?> im) {
182                assert (ImageUtilities.checkSameSize(this, im));
183
184                final int np = this.bands.size();
185
186                for (int i = 0; i < np; i++)
187                        this.bands.get(i).addInplace(((MultiBandImage<?, ?, ?>) im).bands.get(i));
188
189                return (I) this;
190        }
191
192        /**
193         * Adds to each pixel (in all bandS) the value of corresponding pixel in the
194         * given image. Side-affects this image.
195         * 
196         * @param im
197         *            The image to add to this image.
198         * @return A reference to this image containing the result.
199         */
200        @SuppressWarnings("unchecked")
201        public I addInplace(final SingleBandImage<?, ?> im) {
202                assert (ImageUtilities.checkSameSize(this, im));
203
204                final int np = this.bands.size();
205
206                for (int i = 0; i < np; i++)
207                        this.bands.get(i).addInplace(im);
208
209                return (I) this;
210        }
211
212        /**
213         * Add the given value to each pixel in every band. Side-affects this image.
214         * 
215         * @param num
216         *            The value to add to each pixel
217         * @return A reference to this image containing the result.
218         */
219        @SuppressWarnings("unchecked")
220        public I addInplace(final T num) {
221                for (final S sbi : this) {
222                        sbi.addInplace(num);
223                }
224
225                return (I) this;
226        }
227
228        /**
229         * {@inheritDoc}
230         * 
231         * @see org.openimaj.image.Image#addInplace(java.lang.Object)
232         */
233        @SuppressWarnings("unchecked")
234        @Override
235        public I addInplace(final T[] num) {
236                final int np = this.bands.size();
237
238                assert (num.length == np);
239
240                for (int i = 0; i < np; i++)
241                        this.bands.get(i).addInplace(num[i]);
242
243                return (I) this;
244        }
245
246        /**
247         * Sets any pixels that are below min to zero or above max to the highest
248         * normal value that the image allows (usually 1 for floating-point images).
249         * This method may side-affect this image.
250         * 
251         * @param min
252         *            The minimum value to clip to
253         * @param max
254         *            The maximum value to clip to.
255         * @return this
256         * @see Image#clip(Object, Object)
257         */
258        @SuppressWarnings("unchecked")
259        public I clip(final T min, final T max) {
260                for (final S sbi : this) {
261                        sbi.clip(min, max);
262                }
263
264                return (I) this;
265        }
266
267        /**
268         * {@inheritDoc}
269         * 
270         * @see org.openimaj.image.Image#clip(java.lang.Object, java.lang.Object)
271         */
272        @SuppressWarnings("unchecked")
273        @Override
274        public I clip(final T[] min, final T[] max) {
275                final int np = this.bands.size();
276
277                assert (min.length == np);
278                assert (max.length == np);
279
280                for (int i = 0; i < np; i++)
281                        this.bands.get(i).clip(min[i], max[i]);
282
283                return (I) this;
284        }
285
286        /**
287         * For all bands, sets any values above the given threshold to zero.
288         * Side-affects this image.
289         * 
290         * @param thresh
291         *            The threshold above which values are clipped
292         * @return A reference to this image containing the result.
293         */
294        @SuppressWarnings("unchecked")
295        public I clipMax(final T thresh) {
296                for (final S sbm : this)
297                        sbm.clipMax(thresh);
298
299                return (I) this;
300        }
301
302        /**
303         * {@inheritDoc}
304         * 
305         * @see org.openimaj.image.Image#clipMax(java.lang.Object)
306         */
307        @SuppressWarnings("unchecked")
308        @Override
309        public I clipMax(final T[] thresh) {
310                final int np = this.bands.size();
311
312                assert (thresh.length == np);
313
314                for (int i = 0; i < np; i++)
315                        this.bands.get(i).clipMax(thresh[i]);
316
317                return (I) this;
318        }
319
320        /**
321         * Sets all pixels in all bands that have a value below the given threshold
322         * to zero. Side-affects this image.
323         * 
324         * @param thresh
325         *            The threshold below which pixels will be set to zero.
326         * @return A reference to this image containing the result.
327         */
328        @SuppressWarnings("unchecked")
329        public I clipMin(final T thresh) {
330                for (final S sbm : this)
331                        sbm.clipMin(thresh);
332
333                return (I) this;
334        }
335
336        /**
337         * {@inheritDoc}
338         * 
339         * @see org.openimaj.image.Image#clipMin(java.lang.Object)
340         */
341        @SuppressWarnings("unchecked")
342        @Override
343        public I clipMin(final T[] thresh) {
344                final int np = this.bands.size();
345
346                assert (thresh.length == np);
347
348                for (int i = 0; i < np; i++)
349                        this.bands.get(i).clipMin(thresh[i]);
350
351                return (I) this;
352        }
353
354        /**
355         * {@inheritDoc}
356         * 
357         * @see org.openimaj.image.Image#clone()
358         */
359        @Override
360        public I clone() {
361                final I newImage = this.newInstance();
362
363                for (final S sbi : this) {
364                        newImage.bands.add(sbi.clone());
365                }
366                newImage.colourSpace = this.colourSpace;
367
368                return newImage;
369        }
370
371        /**
372         * Delete the band at the given index.
373         * 
374         * @param index
375         *            The index of the band to remove.
376         */
377        public void deleteBand(final int index) {
378                this.bands.remove(index);
379        }
380
381        /**
382         * Divides all pixels of each band by the given value and returns result as
383         * a new image.
384         * 
385         * @param val
386         *            The value to divide every pixel by.
387         * @return A new image containing the result.
388         */
389        public I divide(final T val) {
390                final I newImage = this.clone();
391                newImage.divide(val);
392                return newImage;
393        }
394
395        /**
396         * {@inheritDoc}
397         * 
398         * @see org.openimaj.image.Image#divideInplace(org.openimaj.image.Image)
399         */
400        @Override
401        public I divideInplace(final Image<?, ?> im) {
402                if (im instanceof MultiBandImage<?, ?, ?>) {
403                        return this.divideInplace((MultiBandImage<?, ?, ?>) im);
404                } else if (im instanceof SingleBandImage<?, ?>) {
405                        return this.divideInplace((SingleBandImage<?, ?>) im);
406                } else {
407                        throw new UnsupportedOperationException("Unsupported Type");
408                }
409        }
410
411        /**
412         * Divides the pixels in every band of this image by the corresponding pixel
413         * in the corresponding band of the given image. Side-affects this image.
414         * 
415         * @param im
416         *            The image to divide into this image.
417         * @return A reference to this image containing the result.
418         */
419        @SuppressWarnings("unchecked")
420        public I divideInplace(final MultiBandImage<?, ?, ?> im) {
421                assert (ImageUtilities.checkSameSize(this, im));
422
423                final int np = this.bands.size();
424
425                for (int i = 0; i < np; i++)
426                        this.bands.get(i).divideInplace(((MultiBandImage<?, ?, ?>) im).bands.get(i));
427
428                return (I) this;
429        }
430
431        /**
432         * Divides the pixels in every band of this image by the corresponding pixel
433         * in the given image. Side-affects this image.
434         * 
435         * @param im
436         *            The image to divide into this image.
437         * @return A reference to this image containing the result.
438         */
439        @SuppressWarnings("unchecked")
440        public I divideInplace(final SingleBandImage<?, ?> im) {
441                assert (ImageUtilities.checkSameSize(this, im));
442
443                final int np = this.bands.size();
444
445                for (int i = 0; i < np; i++)
446                        this.bands.get(i).divideInplace(im);
447
448                return (I) this;
449        }
450
451        /**
452         * Divide all pixels of every band by the given value. Side-affects this
453         * image.
454         * 
455         * @param val
456         *            The value to divide by
457         * @return A reference to this image containing the result.
458         */
459        @SuppressWarnings("unchecked")
460        public I divideInplace(final T val) {
461                for (final S sbm : this) {
462                        sbm.divideInplace(val);
463                }
464                return (I) this;
465        }
466
467        /**
468         * {@inheritDoc}
469         * 
470         * @see org.openimaj.image.Image#divideInplace(java.lang.Object)
471         */
472        @SuppressWarnings("unchecked")
473        @Override
474        public I divideInplace(final T[] val) {
475                final int np = this.bands.size();
476
477                assert (val.length == np);
478
479                for (int i = 0; i < np; i++)
480                        this.bands.get(i).divideInplace(val[i]);
481
482                return (I) this;
483        }
484
485        /**
486         * {@inheritDoc}
487         * 
488         * @see org.openimaj.image.Image#extractROI(int, int,
489         *      org.openimaj.image.Image)
490         */
491        @Override
492        public I extractROI(final int x, final int y, final I out) {
493                for (int i = 0; i < this.bands.size(); i++) {
494                        final S img = this.bands.get(i);
495                        img.extractROI(x, y, out.bands.get(i));
496                }
497
498                return out;
499        }
500
501        /**
502         * {@inheritDoc}
503         * 
504         * @see org.openimaj.image.Image#extractROI(int, int, int, int)
505         */
506        @Override
507        public I extractROI(final int x, final int y, final int w, final int h) {
508                final I newImage = this.newInstance();
509
510                for (final S sbm : this) {
511                        newImage.addBand(sbm.extractROI(x, y, w, h));
512                }
513                return newImage;
514        }
515
516        /**
517         * {@inheritDoc}
518         * 
519         * @see org.openimaj.image.Image#fill(java.lang.Object)
520         */
521        @SuppressWarnings("unchecked")
522        @Override
523        public I fill(final T[] colour) {
524                for (int b = 0; b < this.bands.size(); b++)
525                        this.bands.get(b).fill(colour[b]);
526                return (I) this;
527        }
528
529        /**
530         * Flatten the bands into a single band using the average value of the
531         * pixels at each location.
532         * 
533         * @return A new single-band image containing the result.
534         */
535        public S flatten() {
536                if (this.bands.size() == 1)
537                        return bands.get(0).clone();
538
539                final S out = this.newBandInstance(this.getWidth(), this.getHeight());
540
541                for (final S sbm : this)
542                        out.addInplace(sbm);
543
544                return out.divideInplace(this.intToT(this.numBands()));
545        }
546
547        /**
548         * Flatten the bands into a single band by selecting the maximum value pixel
549         * from each band.
550         * 
551         * @return A new flattened image
552         */
553        public abstract S flattenMax();
554
555        /**
556         * Get the band at index i.
557         * 
558         * @param i
559         *            the index
560         * @return the specified colour band
561         */
562        public S getBand(final int i) {
563                return this.bands.get(i);
564        }
565
566        /**
567         * Get the colour space of this image
568         * 
569         * @return the colour space
570         */
571        public ColourSpace getColourSpace() {
572                return this.colourSpace;
573        }
574
575        /**
576         * {@inheritDoc}
577         * 
578         * @see org.openimaj.image.Image#getContentArea()
579         */
580        @Override
581        public Rectangle getContentArea() {
582                int minx = this.getWidth(), maxx = 0, miny = this.getHeight(), maxy = 0;
583                for (int i = 0; i < this.numBands(); i++) {
584                        final Rectangle box = this.getBand(i).getContentArea();
585                        if (box.minX() < minx)
586                                minx = (int) box.minX();
587                        if (box.maxX() > maxx)
588                                maxx = (int) box.maxX();
589                        if (box.minY() < miny)
590                                miny = (int) box.minY();
591                        if (box.maxY() > maxy)
592                                maxy = (int) box.maxY();
593                }
594
595                return new Rectangle(minx, miny, maxx - minx, maxy - miny);
596        }
597
598        /**
599         * {@inheritDoc}
600         * 
601         * @see org.openimaj.image.Image#getField(org.openimaj.image.Image.Field)
602         */
603        @Override
604        public I getField(final Field f) {
605                final I newImage = this.newInstance();
606
607                for (final S sbm : this) {
608                        newImage.bands.add(sbm.getField(f));
609                }
610                return newImage;
611        }
612
613        /**
614         * {@inheritDoc}
615         * 
616         * @see org.openimaj.image.Image#getFieldCopy(org.openimaj.image.Image.Field)
617         */
618        @Override
619        public I getFieldCopy(final Field f) {
620                final I newImage = this.newInstance();
621
622                for (final S sbm : this) {
623                        newImage.bands.add(sbm.getFieldCopy(f));
624                }
625                return newImage;
626        }
627
628        /**
629         * {@inheritDoc}
630         * 
631         * @see org.openimaj.image.Image#getFieldInterpolate(org.openimaj.image.Image.Field)
632         */
633        @Override
634        public I getFieldInterpolate(final Field f) {
635                final I newImage = this.newInstance();
636
637                for (final S sbm : this) {
638                        newImage.bands.add(sbm.getFieldInterpolate(f));
639                }
640
641                return newImage;
642        }
643
644        /**
645         * {@inheritDoc}
646         * 
647         * @see org.openimaj.image.Image#getHeight()
648         */
649        @Override
650        public int getHeight() {
651                if (this.bands.size() > 0)
652                        return this.bands.get(0).getHeight();
653                return 0;
654        }
655
656        /**
657         * {@inheritDoc}
658         * 
659         * @see org.openimaj.image.Image#getWidth()
660         */
661        @Override
662        public int getWidth() {
663                if (this.bands.size() > 0)
664                        return this.bands.get(0).getWidth();
665                return 0;
666        }
667
668        /**
669         * {@inheritDoc}
670         * 
671         * @see org.openimaj.image.Image#internalAssign(org.openimaj.image.Image)
672         */
673        @SuppressWarnings("unchecked")
674        @Override
675        public I internalCopy(final I im)
676        {
677                final int nb = this.bands.size();
678                for (int i = 0; i < nb; i++)
679                        this.bands.get(i).internalCopy(im.getBand(i));
680
681                return (I) this;
682        }
683
684        /**
685         * {@inheritDoc}
686         * 
687         * @see org.openimaj.image.Image#internalAssign(org.openimaj.image.Image)
688         */
689        @SuppressWarnings("unchecked")
690        @Override
691        public I internalAssign(final I im) {
692                this.bands = im.bands;
693                return (I) this;
694        }
695
696        /**
697         * Converts the given integer to a value that can be used as a pixel value.
698         * 
699         * @param n
700         *            The integer to convert.
701         * @return A value that can be used as a pixel value.
702         */
703        protected abstract T intToT(int n);
704
705        /**
706         * {@inheritDoc}
707         * 
708         * @see org.openimaj.image.Image#inverse()
709         */
710        @SuppressWarnings("unchecked")
711        @Override
712        public I inverse() {
713                for (final S sbm : this) {
714                        sbm.inverse();
715                }
716                return (I) this;
717        }
718
719        /**
720         * {@inheritDoc}
721         * 
722         * @see java.lang.Iterable#iterator()
723         */
724        @Override
725        public Iterator<S> iterator() {
726                return this.bands.iterator();
727        }
728
729        /**
730         * {@inheritDoc}
731         * 
732         * @see org.openimaj.image.Image#max()
733         */
734        @Override
735        public T[] max() {
736                final List<T> pixels = new ArrayList<T>();
737
738                for (final S sbm : this) {
739                        pixels.add(sbm.max());
740                }
741
742                return pixels.toArray(createPixelArray(this.numBands()));
743        }
744
745        /**
746         * {@inheritDoc}
747         * 
748         * @see org.openimaj.image.Image#min()
749         */
750        @Override
751        public T[] min() {
752                final List<T> pixels = new ArrayList<T>();
753
754                for (final S sbm : this) {
755                        pixels.add(sbm.min());
756                }
757
758                return pixels.toArray(createPixelArray(this.numBands()));
759        }
760
761        /**
762         * Create an array of n pixels
763         * 
764         * @param n
765         *            number of pixels
766         * @return the array
767         */
768        protected abstract T[] createPixelArray(int n);
769
770        /**
771         * Multiplies each pixel of every band by the given value and returns the
772         * result as a new image.
773         * 
774         * @param num
775         *            The value to multiply by.
776         * @return A new image containing the result.
777         */
778        public I multiply(final T num) {
779                final I newImage = this.clone();
780                newImage.multiplyInplace(num);
781                return newImage;
782        }
783
784        /**
785         * {@inheritDoc}
786         * 
787         * @see org.openimaj.image.Image#multiplyInplace(org.openimaj.image.Image)
788         */
789        @Override
790        public I multiplyInplace(final Image<?, ?> im) {
791                if (im instanceof MultiBandImage<?, ?, ?>) {
792                        return this.multiplyInplace((MultiBandImage<?, ?, ?>) im);
793                } else if (im instanceof SingleBandImage<?, ?>) {
794                        return this.multiplyInplace((SingleBandImage<?, ?>) im);
795                } else {
796                        throw new UnsupportedOperationException("Unsupported Type");
797                }
798        }
799
800        /**
801         * Multiplies every pixel in this image by the corresponding pixel in the
802         * corresponding band in the given image. Side-affects this image.
803         * 
804         * @param im
805         *            The image to multiply with this image.
806         * @return A reference to this image containing the result.
807         */
808        @SuppressWarnings("unchecked")
809        public I multiplyInplace(final MultiBandImage<?, ?, ?> im) {
810                assert (ImageUtilities.checkSameSize(this, im));
811
812                final int np = this.bands.size();
813
814                for (int i = 0; i < np; i++)
815                        this.bands.get(i).multiplyInplace(((MultiBandImage<?, ?, ?>) im).bands.get(i));
816
817                return (I) this;
818        }
819
820        /**
821         * Multiplies every pixel in this image by the corresponding pixel in the
822         * given image. Side-affects this image.
823         * 
824         * @param im
825         *            The image to multiply with this image.
826         * @return A reference to this image containing the result.
827         */
828        @SuppressWarnings("unchecked")
829        public I multiplyInplace(final SingleBandImage<?, ?> im) {
830                assert (ImageUtilities.checkSameSize(this, im));
831
832                final int np = this.bands.size();
833
834                for (int i = 0; i < np; i++)
835                        this.bands.get(i).multiplyInplace(im);
836
837                return (I) this;
838        }
839
840        /**
841         * Multiplies each pixel of every band by the given value. Side-affects this
842         * image.
843         * 
844         * @param num
845         *            The value to multiply this image by
846         * @return A reference to this image containing the result.
847         */
848        @SuppressWarnings("unchecked")
849        public I multiplyInplace(final T num) {
850                for (final S sbm : this)
851                        sbm.multiplyInplace(num);
852
853                return (I) this;
854        }
855
856        /**
857         * {@inheritDoc}
858         * 
859         * @see org.openimaj.image.Image#multiplyInplace(java.lang.Object)
860         */
861        @SuppressWarnings("unchecked")
862        @Override
863        public I multiplyInplace(final T[] num) {
864                final int np = this.bands.size();
865
866                assert (num.length == np);
867
868                for (int i = 0; i < np; i++)
869                        this.bands.get(i).multiplyInplace(num[i]);
870
871                return (I) this;
872        }
873
874        /**
875         * Returns a new instance of an image that represents each band.
876         * 
877         * @param width
878         *            The width of the image
879         * @param height
880         *            The height of the image
881         * @return A new {@link SingleBandImage} of the appropriate type.
882         */
883        public abstract S newBandInstance(int width, int height);
884
885        /**
886         * Returns a new instance of a this image type.
887         * 
888         * @return A new {@link MBFImage} subclass type.
889         */
890        public abstract I newInstance();
891
892        /**
893         * {@inheritDoc}
894         * 
895         * @see org.openimaj.image.Image#newInstance(int, int)
896         */
897        @Override
898        public abstract I newInstance(int width, int height);
899
900        /**
901         * {@inheritDoc}
902         * 
903         * @see org.openimaj.image.Image#normalise()
904         */
905        @SuppressWarnings("unchecked")
906        @Override
907        public I normalise() {
908                for (final S sbm : this)
909                        sbm.normalise();
910
911                return (I) this;
912        }
913
914        /**
915         * Returns the number of bands in this image.
916         * 
917         * @return the number of bands in this image.
918         */
919        public int numBands() {
920                return this.bands.size();
921        }
922
923        /**
924         * {@inheritDoc}
925         * 
926         * @see org.openimaj.image.processor.SinglebandImageProcessor.Processable#process(org.openimaj.image.processor.SinglebandImageProcessor)
927         */
928        @Override
929        public I process(final SinglebandImageProcessor<T, S> p) {
930                final I out = this.newInstance();
931                for (final S sbm : this)
932                        out.bands.add(sbm.process(p));
933
934                return out;
935        }
936
937        /**
938         * {@inheritDoc}
939         * 
940         * @see org.openimaj.image.processor.SinglebandKernelProcessor.Processable#process(org.openimaj.image.processor.SinglebandKernelProcessor)
941         */
942        @Override
943        public I process(final SinglebandKernelProcessor<T, S> kernel) {
944                return this.process(kernel, false);
945        }
946
947        /**
948         * {@inheritDoc}
949         * 
950         * @see org.openimaj.image.processor.SinglebandKernelProcessor.Processable#process(org.openimaj.image.processor.SinglebandKernelProcessor,
951         *      boolean)
952         */
953        @Override
954        public I process(final SinglebandKernelProcessor<T, S> kernel, final boolean pad) {
955                final I out = this.newInstance();
956                for (final S sbm : this)
957                        out.bands.add(sbm.process(kernel, pad));
958
959                return out;
960        }
961
962        /**
963         * Processes this image with the given {@link SinglebandImageProcessor} for
964         * every band.
965         * 
966         * @param pp
967         *            The pixel process to apply to each band in turn.
968         * @return A new image containing the result.
969         */
970        public I process(final SinglebandPixelProcessor<T> pp) {
971                final I out = this.newInstance();
972                for (final S sbm : this)
973                        out.bands.add(sbm.process(pp));
974
975                return out;
976        }
977
978        /**
979         * {@inheritDoc}
980         * 
981         * @see org.openimaj.image.processor.SinglebandImageProcessor.Processable#processInplace(org.openimaj.image.processor.SinglebandImageProcessor)
982         */
983        @Override
984        @SuppressWarnings("unchecked")
985        public I processInplace(final SinglebandImageProcessor<T, S> p) {
986                for (final S sbm : this)
987                        sbm.processInplace(p);
988
989                return (I) this;
990        }
991
992        /**
993         * {@inheritDoc}
994         * 
995         * @see org.openimaj.image.processor.SinglebandKernelProcessor.Processable#processInplace(org.openimaj.image.processor.SinglebandKernelProcessor)
996         */
997        @Override
998        public I processInplace(final SinglebandKernelProcessor<T, S> kernel) {
999                return this.processInplace(kernel, false);
1000        }
1001
1002        /**
1003         * {@inheritDoc}
1004         * 
1005         * @see org.openimaj.image.processor.SinglebandKernelProcessor.Processable#processInplace(org.openimaj.image.processor.SinglebandKernelProcessor,
1006         *      boolean)
1007         */
1008        @Override
1009        @SuppressWarnings("unchecked")
1010        public I processInplace(final SinglebandKernelProcessor<T, S> kernel, final boolean pad) {
1011                for (final S sbm : this)
1012                        sbm.processInplace(kernel, pad);
1013
1014                return (I) this;
1015        }
1016
1017        /**
1018         * Process this image with the given {@link SinglebandImageProcessor} for
1019         * every band. Side-affects this image.
1020         * 
1021         * @param pp
1022         *            The pixel processor to apply to each band in turn.
1023         * @return A reference to this image containing the result.
1024         */
1025        @SuppressWarnings("unchecked")
1026        public I processInplace(final SinglebandPixelProcessor<T> pp) {
1027                for (final S sbm : this)
1028                        sbm.processInplace(pp);
1029
1030                return (I) this;
1031        }
1032
1033        /**
1034         * {@inheritDoc}
1035         * 
1036         * @see org.openimaj.image.Image#setPixel(int, int, java.lang.Object)
1037         */
1038        @Override
1039        public void setPixel(final int x, final int y, final T[] val) {
1040                final int np = this.bands.size();
1041                if (np == val.length)
1042                        for (int i = 0; i < np; i++)
1043                                this.bands.get(i).setPixel(x, y, val[i]);
1044                else {
1045                        final int offset = val.length - np;
1046                        for (int i = 0; i < np; i++)
1047                                if (i + offset >= 0)
1048                                        this.bands.get(i).setPixel(x, y, val[i + offset]);
1049                }
1050        }
1051
1052        /**
1053         * Subtracts the given value from every pixel in every band and returns the
1054         * result as a new image.
1055         * 
1056         * @param num
1057         *            The value to subtract from this image.
1058         * @return A new image containing the result.
1059         */
1060        public I subtract(final T num) {
1061                final I newImage = this.clone();
1062                newImage.subtract(num);
1063                return newImage;
1064        }
1065
1066        /**
1067         * {@inheritDoc}
1068         * 
1069         * @see org.openimaj.image.Image#subtractInplace(org.openimaj.image.Image)
1070         */
1071        @Override
1072        public I subtractInplace(final Image<?, ?> im) {
1073                if (im instanceof MultiBandImage<?, ?, ?>) {
1074                        return this.subtractInplace((MultiBandImage<?, ?, ?>) im);
1075                } else if (im instanceof SingleBandImage<?, ?>) {
1076                        return this.subtractInplace((SingleBandImage<?, ?>) im);
1077                } else {
1078                        throw new UnsupportedOperationException("Unsupported Type");
1079                }
1080        }
1081
1082        /**
1083         * Subtracts from every pixel in every band the corresponding pixel value in
1084         * the corresponding band of the given image. Side-affects this image.
1085         * 
1086         * @param im
1087         *            The image to subtract from this image
1088         * @return A reference to this image containing the result.
1089         */
1090        @SuppressWarnings("unchecked")
1091        public I subtractInplace(final MultiBandImage<?, ?, ?> im) {
1092                assert (ImageUtilities.checkSameSize(this, im));
1093
1094                final int np = this.bands.size();
1095
1096                for (int i = 0; i < np; i++)
1097                        this.bands.get(i).subtractInplace(((MultiBandImage<?, ?, ?>) im).bands.get(i));
1098
1099                return (I) this;
1100        }
1101
1102        /**
1103         * Subtracts from every pixel in every band the corresponding pixel value in
1104         * the given image. Side-affects this image.
1105         * 
1106         * @param im
1107         *            The image to subtract from this image.
1108         * @return A reference to this image containing the result.
1109         */
1110        @SuppressWarnings("unchecked")
1111        public I subtractInplace(final SingleBandImage<?, ?> im) {
1112                assert (ImageUtilities.checkSameSize(this, im));
1113
1114                final int np = this.bands.size();
1115
1116                for (int i = 0; i < np; i++)
1117                        this.bands.get(i).subtractInplace(im);
1118
1119                return (I) this;
1120        }
1121
1122        /**
1123         * Subtracts the given value from every pixel in every band. Side-affects
1124         * this image.
1125         * 
1126         * @param num
1127         *            The value to subtract from this image
1128         * @return A reference to this image containing the result.
1129         */
1130        @SuppressWarnings("unchecked")
1131        public I subtractInplace(final T num) {
1132                for (final S sbm : this)
1133                        sbm.subtractInplace(num);
1134
1135                return (I) this;
1136        }
1137
1138        /**
1139         * {@inheritDoc}
1140         * 
1141         * @see org.openimaj.image.Image#subtractInplace(java.lang.Object)
1142         */
1143        @SuppressWarnings("unchecked")
1144        @Override
1145        public I subtractInplace(final T[] num) {
1146                final int np = this.bands.size();
1147
1148                assert (num.length == np);
1149
1150                for (int i = 0; i < np; i++)
1151                        this.bands.get(i).subtractInplace(num[i]);
1152
1153                return (I) this;
1154        }
1155
1156        /**
1157         * Sets the value of any pixel below the given threshold to zero and all
1158         * others to 1 for all bands. Side-affects this image.
1159         * 
1160         * @param thresh
1161         *            The threshold above which pixels will be set to 1.
1162         * @return A reference to this image containing the result.
1163         */
1164        @SuppressWarnings("unchecked")
1165        public I threshold(final T thresh) {
1166                for (final S sbm : this)
1167                        sbm.threshold(thresh);
1168
1169                return (I) this;
1170        }
1171
1172        /**
1173         * {@inheritDoc}
1174         * 
1175         * @see org.openimaj.image.Image#threshold(java.lang.Object)
1176         */
1177        @SuppressWarnings("unchecked")
1178        @Override
1179        public I threshold(final T[] thresh) {
1180                final int np = this.bands.size();
1181
1182                assert (thresh.length == np);
1183
1184                for (int i = 0; i < np; i++)
1185                        this.bands.get(i).threshold(thresh[i]);
1186
1187                return (I) this;
1188        }
1189
1190        /**
1191         * {@inheritDoc}
1192         * 
1193         * @see org.openimaj.image.Image#toByteImage()
1194         */
1195        @Override
1196        public byte[] toByteImage() {
1197                final int width = this.getWidth();
1198                final int height = this.getHeight();
1199                final int nb = this.bands.size();
1200
1201                final byte[] ppmData = new byte[nb * height * width];
1202
1203                for (int n = 0; n < nb; n++) {
1204                        final byte[] band = this.bands.get(n).toByteImage();
1205
1206                        for (int j = 0; j < height; j++) {
1207                                for (int i = 0; i < width; i++) {
1208                                        ppmData[nb * (i + j * width) + n] = band[i + j * width];
1209                                }
1210                        }
1211                }
1212                return ppmData;
1213        }
1214
1215        /**
1216         * {@inheritDoc}
1217         * 
1218         * @see org.openimaj.image.Image#toPackedARGBPixels()
1219         */
1220        @Override
1221        public int[] toPackedARGBPixels() {
1222                // TODO: deal better with color spaces
1223                if (this.bands.size() == 1) {
1224                        return this.bands.get(0).toPackedARGBPixels();
1225                } else if (this.bands.size() == 3) {
1226                        final int width = this.getWidth();
1227                        final int height = this.getHeight();
1228
1229                        final byte[] rp = this.bands.get(0).toByteImage();
1230                        final byte[] gp = this.bands.get(1).toByteImage();
1231                        final byte[] bp = this.bands.get(2).toByteImage();
1232
1233                        final int[] data = new int[height * width];
1234
1235                        for (int r = 0; r < height; r++) {
1236                                for (int c = 0; c < width; c++) {
1237                                        final int red = rp[c + r * width] & 0xff;
1238                                        final int green = gp[c + r * width] & 0xff;
1239                                        final int blue = bp[c + r * width] & 0xff;
1240
1241                                        final int rgb = 0xff << 24 | red << 16 | green << 8 | blue;
1242                                        data[c + r * width] = rgb;
1243                                }
1244                        }
1245
1246                        return data;
1247                } else if (this.bands.size() == 4) {
1248                        final int width = this.getWidth();
1249                        final int height = this.getHeight();
1250
1251                        final byte[] ap = this.bands.get(3).toByteImage();
1252                        final byte[] rp = this.bands.get(0).toByteImage();
1253                        final byte[] gp = this.bands.get(1).toByteImage();
1254                        final byte[] bp = this.bands.get(2).toByteImage();
1255
1256                        final int[] data = new int[height * width];
1257
1258                        for (int r = 0; r < height; r++) {
1259                                for (int c = 0; c < width; c++) {
1260                                        final int alpha = ap[c + r * width] & 0xff;
1261                                        final int red = rp[c + r * width] & 0xff;
1262                                        final int green = gp[c + r * width] & 0xff;
1263                                        final int blue = bp[c + r * width] & 0xff;
1264
1265                                        final int argb = alpha << 24 | red << 16 | green << 8 | blue;
1266                                        data[c + r * width] = argb;
1267                                }
1268                        }
1269
1270                        return data;
1271                } else {
1272                        throw new UnsupportedOperationException(
1273                                        "Unable to create bufferedImage with " + this.numBands() + " bands");
1274                }
1275        }
1276
1277        /**
1278         * {@inheritDoc}
1279         * 
1280         * @see org.openimaj.image.Image#zero()
1281         */
1282        @SuppressWarnings("unchecked")
1283        @Override
1284        public I zero() {
1285                for (final S sbm : this)
1286                        sbm.zero();
1287
1288                return (I) this;
1289        }
1290
1291        @SuppressWarnings("unchecked")
1292        @Override
1293        public I shiftLeftInplace(final int count) {
1294                for (final S b : this.bands)
1295                        b.shiftLeftInplace(count);
1296                return (I) this;
1297        }
1298
1299        @SuppressWarnings("unchecked")
1300        @Override
1301        public I shiftRightInplace(final int count) {
1302                for (final S b : this.bands)
1303                        b.shiftRightInplace(count);
1304                return (I) this;
1305        }
1306
1307        @SuppressWarnings("unchecked")
1308        @Override
1309        public I flipX() {
1310                for (final S b : this.bands)
1311                        b.flipX();
1312
1313                return (I) this;
1314        }
1315
1316        @SuppressWarnings("unchecked")
1317        @Override
1318        public I flipY() {
1319                for (final S b : this.bands)
1320                        b.flipY();
1321
1322                return (I) this;
1323        }
1324
1325        @Override
1326        @SuppressWarnings("unchecked")
1327        public boolean equals(final Object other) {
1328                final I that = (I) other;
1329                if (this.bands.size() != that.bands.size())
1330                        return false;
1331                int i = 0;
1332                for (final S b : this.bands)
1333                {
1334                        final boolean fail = !b.equals(that.getBand(i));
1335                        if (fail)
1336                                return false;
1337
1338                        i++;
1339                }
1340
1341                return true;
1342        }
1343
1344        @SuppressWarnings("unchecked")
1345        @Override
1346        public I replace(final T[] target, final T[] replacement) {
1347                for (int b = 0; b < this.bands.size(); b++)
1348                        this.bands.get(b).replace(target[b], replacement[b]);
1349                return (I) this;
1350        }
1351
1352        @Override
1353        public I extractCentreSubPix(float cx, float cy, I out) {
1354                for (int b = 0; b < this.bands.size(); b++)
1355                        this.bands.get(b).extractCentreSubPix(cx, cy, out.bands.get(b));
1356                return out;
1357        }
1358}