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