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}