org.openimaj.image.MBFImage Maven / Gradle / Ivy
/**
* Copyright (c) 2011, The University of Southampton and the individual contributors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the University of Southampton nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.openimaj.image;
import java.util.Comparator;
import org.openimaj.image.colour.ColourSpace;
import org.openimaj.image.pixel.Pixel;
import org.openimaj.image.renderer.MBFImageRenderer;
import org.openimaj.image.renderer.RenderHints;
/**
* A multiband floating-point image.
*
* @author Jonathon Hare ([email protected])
*/
public class MBFImage extends MultiBandImage {
private static final long serialVersionUID = 1L;
/**
* Construct an empty MBFImage with a the default RGB colourspace
*/
public MBFImage() {
super(ColourSpace.RGB);
}
/**
* Construct an MBFImage from single band images. The given images are used
* directly as the bands and are not cloned.
*
* @param colourSpace
* the colourspace
* @param images
* the bands
*/
public MBFImage(final ColourSpace colourSpace, final FImage... images) {
super(colourSpace, images);
}
/**
* Construct an MBFImage from single band images with the default RGB
* colourspace if there are three images, RGBA if there are 4 images, or
* CUSTOM otherwise. The given images are used directly as the bands and are
* not cloned; if you want to create an RGB {@link MBFImage} from a single
* {@link FImage}, you would need to clone the {@link FImage} at least
* twice.
*
* @param images
* the bands
*/
public MBFImage(final FImage... images) {
super(images.length == 3 ? ColourSpace.RGB : images.length == 4 ? ColourSpace.RGBA : ColourSpace.CUSTOM, images);
}
/**
* Construct an empty RGB image (3 bands)
*
* @param width
* Width of image
* @param height
* Height of image
*/
public MBFImage(final int width, final int height) {
this(width, height, ColourSpace.RGB);
}
/**
* Construct an empty image
*
* @param width
* Width of image
* @param height
* Height of image
* @param colourSpace
* the colourspace
*/
public MBFImage(final int width, final int height, final ColourSpace colourSpace) {
this.colourSpace = colourSpace;
for (int i = 0; i < colourSpace.getNumBands(); i++) {
this.bands.add(new FImage(width, height));
}
}
/**
* Construct an empty image. If the number of bands is 3, RGB is assumed, if
* the number is 4, then RGBA is assumed, otherwise the colourspace is set
* to CUSTOM.
*
* @param width
* Width of image
* @param height
* Height of image
* @param nbands
* number of bands
*/
public MBFImage(final int width, final int height, final int nbands) {
if (nbands == 3)
this.colourSpace = ColourSpace.RGB;
else if (nbands == 4)
this.colourSpace = ColourSpace.RGBA;
for (int i = 0; i < nbands; i++) {
this.bands.add(new FImage(width, height));
}
}
/**
* Create an image from a BufferedImage object. Resultant image have RGB
* bands in the 0-1 range.
*
* @param data
* array of packed ARGB pixels
* @param width
* the image width
* @param height
* the image height
*/
public MBFImage(final int[] data, final int width, final int height) {
this(data, width, height, false);
}
/**
* Create an image from a int[] object. Resultant image will be in the 0-1
* range. If alpha is true, bands will be RGBA, otherwise RGB
*
* @param data
* array of packed ARGB pixels
* @param width
* the image width
* @param height
* the image height
* @param alpha
* should we load the alpha channel
*/
public MBFImage(final int[] data, final int width, final int height, final boolean alpha) {
this(width, height, alpha ? 4 : 3);
this.internalAssign(data, width, height);
}
/**
* Create an MBFImage from an array of double values with the given width
* and height. The length of the array must equal the width multiplied by
* the height by the number of bands. The values will be downcast to floats.
* The data can either be interlaced (rgbrgb...) or band at a time (rrrr...
* gggg... bbbb...).
*
* @param ds
* An array of floating point values.
* @param width
* The width of the resulting image.
* @param height
* The height of the resulting image.
* @param nbands
* the number of bands
* @param interlaced
* if true the data in the array is interlaced
*/
public MBFImage(double[] ds, int width, int height, int nbands, boolean interlaced) {
if (interlaced) {
for (int i = 0; i < nbands; i++) {
bands.add(new FImage(width, height));
}
for (int y = 0, c = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
for (int i = 0; i < nbands; i++, c++) {
bands.get(i).pixels[y][x] = (float) ds[c];
}
}
}
} else {
for (int i = 0; i < nbands; i++) {
bands.add(new FImage(ds, width, height, i * width * height));
}
}
}
/*
* (non-Javadoc)
*
* @see uk.ac.soton.ecs.jsh2.image.MultiBandImage#flattenMax()
*/
@Override
public FImage flattenMax() {
final int width = this.getWidth();
final int height = this.getHeight();
final FImage out = new FImage(width, height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float max = (this.bands.get(0)).pixels[y][x];
for (int i = 1; i < this.numBands(); i++)
if (max < (this.bands.get(i)).pixels[y][x])
max = (this.bands.get(i)).pixels[y][x];
out.pixels[y][x] = max;
}
}
return out;
}
@Override
public FImage flatten() {
// overly optimised flatten
final int width = this.getWidth();
final int height = this.getHeight();
final FImage out = new FImage(width, height);
final float[][] outp = out.pixels;
final int nb = this.numBands();
for (int i = 1; i < nb; i++) {
final float[][] bnd = this.bands.get(i).pixels;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
outp[y][x] += bnd[y][x];
}
}
}
final float norm = 1f / nb;
final float[][] bnd = this.bands.get(0).pixels;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
outp[y][x] = (outp[y][x] + bnd[y][x]) * norm;
}
}
return out;
}
/*
* (non-Javadoc)
*
* @see uk.ac.soton.ecs.jsh2.image.Image#getPixel(int, int)
*/
@Override
public Float[] getPixel(final int x, final int y) {
final Float[] pixels = new Float[this.bands.size()];
for (int i = 0; i < this.bands.size(); i++) {
pixels[i] = this.bands.get(i).getPixel(x, y);
}
return pixels;
}
@Override
public Comparator super Float[]> getPixelComparator() {
return new Comparator() {
@Override
public int compare(final Float[] o1, final Float[] o2) {
int sumDiff = 0;
boolean anyDiff = false;
for (int i = 0; i < o1.length; i++) {
sumDiff += o1[i] - o2[i];
anyDiff = sumDiff != 0 || anyDiff;
}
if (anyDiff) {
if (sumDiff > 0)
return 1;
else
return -1;
} else
return 0;
}
};
}
/*
* (non-Javadoc)
*
* @see uk.ac.soton.ecs.jsh2.image.Image#getPixelInterp(double, double)
*/
@Override
public Float[] getPixelInterp(final double x, final double y) {
final Float[] result = new Float[this.bands.size()];
for (int i = 0; i < this.bands.size(); i++) {
result[i] = this.bands.get(i).getPixelInterp(x, y);
}
return result;
}
/*
* (non-Javadoc)
*
* @see uk.ac.soton.ecs.jsh2.image.Image#getPixelInterp(double,
* double,Float[])
*/
@Override
public Float[] getPixelInterp(final double x, final double y, final Float[] b) {
final Float[] result = new Float[this.bands.size()];
for (int i = 0; i < this.bands.size(); i++) {
result[i] = this.bands.get(i).getPixelInterp(x, y, b[i]);
}
return result;
}
/**
* Assign planar RGB bytes (R1G1B1R2G2B2...) to this image.
*
* @param bytes
* the byte array
* @param width
* the width of the byte image
* @param height
* the height of the byte image
* @return this
*/
public MBFImage internalAssign(final byte[] bytes, final int width, final int height) {
if (this.getWidth() != width || this.getHeight() != height)
this.internalAssign(this.newInstance(width, height));
final float[][] br = this.bands.get(0).pixels;
final float[][] bg = this.bands.get(1).pixels;
final float[][] bb = this.bands.get(2).pixels;
for (int i = 0, y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
final int blue = bytes[i++] & 0xff;
final int green = (bytes[i++]) & 0xff;
final int red = (bytes[i++]) & 0xff;
br[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[red];
bg[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[green];
bb[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[blue];
}
}
return this;
}
/*
* (non-Javadoc)
*
* @see org.openimaj.image.Image#internalAssign(int[], int, int)
*/
@Override
public MBFImage internalAssign(final int[] data, final int width, final int height) {
if (this.getWidth() != width || this.getHeight() != height)
this.internalAssign(this.newInstance(width, height));
final float[][] br = this.bands.get(0).pixels;
final float[][] bg = this.bands.get(1).pixels;
final float[][] bb = this.bands.get(2).pixels;
float[][] ba = null;
if (this.colourSpace == ColourSpace.RGBA)
ba = this.bands.get(3).pixels;
for (int i = 0, y = 0; y < height; y++) {
for (int x = 0; x < width; x++, i++) {
final int rgb = data[i];
final int alpha = ((rgb >> 24) & 0xff);
final int red = ((rgb >> 16) & 0xff);
final int green = ((rgb >> 8) & 0xff);
final int blue = ((rgb) & 0xff);
br[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[red];
bg[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[green];
bb[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[blue];
if (ba != null)
ba[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[alpha];
}
}
return this;
}
@Override
protected Float intToT(final int n) {
return (float) n;
}
@Override
public FImage newBandInstance(final int width, final int height) {
return new FImage(width, height);
}
@Override
public MBFImage newInstance() {
return new MBFImage();
}
/*
* (non-Javadoc)
*
* @see uk.ac.soton.ecs.jsh2.image.MultiBandImage#newInstance(int, int)
*/
@Override
public MBFImage newInstance(final int width, final int height) {
final MBFImage ret = new MBFImage(width, height, this.bands.size());
ret.colourSpace = this.colourSpace;
return ret;
}
@Override
public MBFImageRenderer createRenderer() {
return new MBFImageRenderer(this);
}
@Override
public MBFImageRenderer createRenderer(final RenderHints options) {
return new MBFImageRenderer(this, options);
}
/**
* Get the value of the pixel at coordinate p
*
* @param p
* The coordinate to get
*
* @return The pixel value at (x, y)
*/
public float[] getPixelNative(final Pixel p) {
return this.getPixelNative(p.x, p.y);
}
/**
* Get the value of the pixel at coordinate (x, y)
.
*
* @param x
* The x-coordinate to get
* @param y
* The y-coordinate to get
*
* @return The pixel value at (x, y)
*/
public float[] getPixelNative(final int x, final int y) {
final float[] pixels = new float[this.bands.size()];
for (int i = 0; i < this.bands.size(); i++) {
pixels[i] = this.bands.get(i).getPixelNative(x, y);
}
return pixels;
}
/**
* Returns the pixels in this image as a vector (an array of the pixel
* type).
*
* @param f
* The array into which to place the data
* @return The pixels in the image as a vector (a reference to the given
* array).
*/
public float[][] getPixelVectorNative(final float[][] f) {
for (int y = 0; y < this.getHeight(); y++)
for (int x = 0; x < this.getWidth(); x++)
f[x + y * this.getWidth()] = this.getPixelNative(x, y);
return f;
}
/**
* Sets the pixel at (x,y)
to the given value. Side-affects
* this image.
*
* @param x
* The x-coordinate of the pixel to set
* @param y
* The y-coordinate of the pixel to set
* @param val
* The value to set the pixel to.
*/
public void setPixelNative(final int x, final int y, final float[] val) {
final int np = this.bands.size();
if (np == val.length)
for (int i = 0; i < np; i++)
this.bands.get(i).setPixel(x, y, val[i]);
else {
final int offset = val.length - np;
for (int i = 0; i < np; i++)
if (i + offset >= 0)
this.bands.get(i).setPixel(x, y, val[i + offset]);
}
}
/**
* {@inheritDoc}
*
* This method assumes the last band in the multiband image is the alpha
* channel. This allows a 2-channel MBFImage where the first image is an
* FImage and the second an alpha channel, as well as a standard RGBA image.
*
* @see org.openimaj.image.Image#overlayInplace(org.openimaj.image.Image,
* int, int)
*/
@Override
public MBFImage overlayInplace(final MBFImage image, final int x, final int y) {
// Assume the alpha channel is the last band
final FImage alpha = image.getBand(image.numBands() - 1);
for (int i = 0; i < this.numBands(); i++)
this.bands.get(i).overlayInplace(image.bands.get(i), alpha, x, y);
return this;
}
/**
* Create a random RGB image.
*
* @param width
* the width
* @param height
* the height
* @return the image
*/
public static MBFImage randomImage(final int width, final int height) {
final MBFImage img = new MBFImage();
img.colourSpace = ColourSpace.RGB;
for (int i = 0; i < 3; i++) {
img.bands.add(FImage.randomImage(width, height));
}
return img;
}
/**
* Convenience method to create an RGB {@link MBFImage} from an
* {@link FImage} by cloning the {@link FImage} for each of the R, G and B
* bands.
*
* @param image
* the {@link FImage} to convert
* @return the new RGB {@link MBFImage}
*/
public static MBFImage createRGB(final FImage image) {
return new MBFImage(image.clone(), image.clone(), image.clone());
}
@Override
public MBFImage fill(final Float[] colour)
{
return super.fill(this.colourSpace.sanitise(colour));
}
@Override
public void setPixel(final int x, final int y, final Float[] val)
{
// Check if we have an alpha channel. If we do, we'll use alpha
// compositing, otherwise, we'll simply copy the pixel colour into
// the pixel position.
if (this.colourSpace == ColourSpace.RGBA && this.numBands() >= 4 && val.length >= 4
&& x >= 0 && x < this.getWidth() && y >= 0 && y < this.getHeight())
{
final float[] p = ImageUtilities.alphaCompositePixel(this.getPixel(x, y), val);
this.getBand(0).pixels[y][x] = p[0];
this.getBand(1).pixels[y][x] = p[1];
this.getBand(2).pixels[y][x] = p[2];
if (this.numBands() >= 4)
this.getBand(3).pixels[y][x] = p[3];
}
else
super.setPixel(x, y, val);
}
@Override
protected Float[] createPixelArray(int n) {
return new Float[n];
}
/**
* Returns the pixels of the image as a vector (array) of doubles.
*
* @return the pixels of the image as a vector (array) of doubles.
*/
public double[] getDoublePixelVector()
{
final int height = getHeight();
final int width = getWidth();
final double f[] = new double[width * height * this.numBands()];
for (int i = 0; i < this.bands.size(); i++) {
final float[][] pixels = bands.get(i).pixels;
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
f[x + y * width + i * height * width] = pixels[y][x];
}
return f;
}
}