package fr.unistra.pelican; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; /** This class represents an integer valued image * Range for an integer pixel is [Integer.MIN_VALUE; Integer.MAX_VALUE] * @author PELICAN team * @version 1.0 */ public class IntegerImage extends Image implements Serializable { /** * Pixel data array */ private int[] pixels; /** * Serial version ID */ private static final long serialVersionUID = 3L; /** * Constructs an IntegerImage */ protected IntegerImage(){ super(); } /** * Constructs an IntegerImage identical to the given argument * @param image IntegerImage to copy */ public IntegerImage(IntegerImage image) { super(image); this.pixels = (int[])image.pixels.clone(); } /** * Constructs a IntegerImage identical to the given argument * @param image Image to copy */ public IntegerImage(Image image) { this(image, true); } /** * Constructs a IntegerImage identical to the given argument * @param image Image to copy * @param copyData if and only if it is set to true are the pixels copied */ public IntegerImage(Image image, boolean copyData) { super(image); this.pixels = new int[image.getXDim() * image.getYDim() * image.getZDim() * image.getTDim() * image.getBDim()]; if(copyData == true) for(int i = 0; i < pixels.length; i++) setPixelInt(i, image.getPixelInt(i)); } /** * Constructs an IntegerImage from the given argument. The pixels are copied if and only of ''copy'' is set to true. * @param image IntegerImage to copy * @param copyData if and only if it is set to true are the pixels copied */ public IntegerImage(IntegerImage image, boolean copyData) { super(image); if(copyData == true) this.pixels = (int[])image.pixels.clone(); else this.pixels = new int[image.getXDim() * image.getYDim() * image.getZDim() * image.getTDim() * image.getBDim()]; } /** * Constructs an IntegerImage with the given dimensions * @param xdim the horizontal dimension * @param ydim the vertical dimension * @param zdim the depth * @param tdim the frame number * @param bdim the channel number */ public IntegerImage(int xdim, int ydim, int zdim, int tdim, int bdim) { super(xdim,ydim,zdim,tdim,bdim); this.pixels = new int[xdim * ydim * zdim * tdim * bdim]; } /** * Sets the pixels to the copy of the given array * @param values pixel array to copy */ public void setPixels(int[] values) { pixels = (int[])values.clone(); } /** * Sets all the pixel values to the given value * @param b Desired value for the pixels */ public void fill(int b) { Arrays.fill(pixels, b); } @Override public void fill(double d) { this.fill(doubleToInt(d)); } /** * Creates a copy of this IntegerImage * @return an exact copy of this IntegerImage */ public IntegerImage copyImage(boolean copyData) { return new IntegerImage(this,copyData); } /** * Creates a new instance of IntegerImage * @param xdim the horizontal dimension * @param ydim the vertical dimension * @param zdim the depth * @param tdim the frame number * @param bdim the channel number */ public Image newInstance(int xdim,int ydim,int zdim,int tdim,int bdim) { return new IntegerImage(xdim,ydim,zdim,tdim,bdim); } /** * Computes the total number of pixels in all dimensions * @return the number of pixels */ public int size() { return pixels.length; } /** * Convert the IntegerImage to a ByteImage without modifying the values * @return a byte image */ public ByteImage copyToByteImage() { ByteImage b=new ByteImage(this,false); for (int p=0;p<b.size();p++) b.setPixelByte(p,getPixelInt(p)); return b; } /** * Convert the IntegerImage to a ByteImage using the scaleToVisibleRange method * @return a byte image */ public ByteImage convertToByteImage() { IntegerImage i=scaleToVisibleRange(); ByteImage b=new ByteImage(i,false); for (int p=0;p<b.size();p++) b.setPixelByte(p,i.getPixelByte(p)); return b; } /** * Scales all values to [MIN_VALUE,MAX_VALUE] for visualisation purposes. * * @return the scaled image */ public IntegerImage scaleToVisibleRange() { //TODO Fusion with convertToByteImage and check that nothing goes wrong without the byte cast IntegerImage d = (IntegerImage) this.copyImage(false); int min = Integer.MAX_VALUE; int max = Integer.MIN_VALUE; for (int p = 0; p < size(); p++) { int tmp = this.getPixelInt(p); if (min > tmp) min = tmp; if (max < tmp) max = tmp; } //int dist = max - min; long dist = (long)max - (long)min; double value; if (max != min) { for (int p = 0; p < size(); p++) { int tmp = this.getPixelInt(p); //tmp = (tmp - min) * 255 / dist; //tmp = (int)((double) (tmp - min) / dist * 255); value = ((double) ((long)tmp - (long)min) / dist); d.setPixelDouble(p, value); } } else System.err.println("Scaling error"); return d; } // /** // * Scales all channels and frames independently to [0,255] for visualisation purposes. // * As the relative contrast gets messed up, even if the channel in question is already in [0,255] it is stretched all the same. // * @return an integer image with pixel values in [0,255] // */ // public IntegerImage scaleToVisibleRange() // { // IntegerImage d = (IntegerImage)this.copyImage(false); // // // the scaling must be realized on XYZ volumes for every channel/frame separately // for(int b = 0; b < bdim; b++) // for(int t = 0; t < tdim; t++){ // int min = Integer.MAX_VALUE; // int max = Integer.MIN_VALUE; // // // get the extrema // for(int z = 0; z < zdim; z++) // for(int x = 0; x < xdim; x++) // for(int y = 0; y < ydim; y++){ // int tmp = this.getPixelXYZTBInt(x,y,z,t,b); // if(min > tmp) min = tmp; // if(max < tmp) max = tmp; // } // // int dist = max - min; // // if(max != min){ // for(int z = 0; z < zdim; z++) // for(int x = 0; x < xdim; x++) // for(int y = 0; y < ydim; y++){ // int tmp = this.getPixelXYZTBInt(x,y,z,t,b); // //tmp = (tmp - min) * 255 / dist; // tmp = (int)((double) (tmp - min) / dist * 255); // d.setPixelXYZTBByte(x,y,z,t,b,tmp); // } // // }else{ // for(int z = 0; z < zdim; z++) // for(int x = 0; x < xdim; x++) // for(int y = 0; y < ydim; y++) // d.setPixelXYZTBInt(x,y,z,t,b,min); // } // } // // return d; // } /** * Scales all values to [0,1] for visualisation purposes. * TODO delete this method if it is as useless as it seems * @return the scaled image */ public DoubleImage normalise() { DoubleImage d = new DoubleImage(this,false); double min = Double.MAX_VALUE; double max = Double.MIN_VALUE; for (int p = 0; p < size(); p++) { double tmp = this.getPixelDouble(p); if (min > tmp) min = tmp; if (max < tmp) max = tmp; } double dist = max - min; if (max != min) { for (int p = 0; p < size(); p++) { double tmp = this.getPixelInt(p); tmp = (tmp - min) / dist; d.setPixelDouble(p, tmp); } } else System.err.println("Scaling error"); return d; } // /** // * Scales all channels and frames independently to [0,1] for normalisation purposes. // * As the relative contrast gets messed up, even if the channel in question is already in [0,1] it is stretched all the same. // * @return a double image with pixel values in [0,1] // */ // public DoubleImage normalise() // { // DoubleImage d = new DoubleImage(this,false); // // // the scaling must be realized on XYZ volumes for every channel/frame separately // for(int b = 0; b < bdim; b++) // for(int t = 0; t < tdim; t++){ // int min = Integer.MAX_VALUE; // int max = Integer.MIN_VALUE; // // // get the extrema // for(int z = 0; z < zdim; z++) // for(int x = 0; x < xdim; x++) // for(int y = 0; y < ydim; y++){ // int tmp = this.getPixelXYZTBInt(x,y,z,t,b); // if(min > tmp) min = tmp; // if(max < tmp) max = tmp; // } // // int dist = max - min; // // if(max != min){ // for(int z = 0; z < zdim; z++) // for(int x = 0; x < xdim; x++) // for(int y = 0; y < ydim; y++){ // double tmp = this.getPixelXYZTBInt(x,y,z,t,b); // //tmp = (tmp - min) * 255 / dist; // tmp = (double) (tmp - min) / dist; // d.setPixelXYZTBDouble(x,y,z,t,b,tmp); // } // // }else{ // for(int z = 0; z < zdim; z++) // for(int x = 0; x < xdim; x++) // for(int y = 0; y < ydim; y++) // d.setPixelXYZTBDouble(x,y,z,t,b,0); // } // } // // return d; // } @Override public double getPixelDouble(int loc) { return intToDouble(pixels[loc]); //return DoubleImage.intToDouble*(double)pixels[loc] + 0.5; } @Override public int getPixelInt(int loc) { return (pixels[loc]); } @Override public int getPixelByte(int loc) { return intToUnsignedByte(pixels[loc]); //return (pixels[loc] >> 24) - Byte.MIN_VALUE; } @Override public boolean getPixelBoolean(int loc) { return intToBoolean(pixels[loc]); //return (pixels[loc] > Integer.MAX_VALUE/2) ? true : false; } @Override public void setPixelDouble(int loc, double value) { pixels[loc] = doubleToInt(value); //pixels[loc] = (int)((value - 0.5) * doubleToInt); } @Override public void setPixelInt(int loc, int value) { pixels[loc] = value; } @Override public void setPixelByte(int loc, int value) { pixels[loc] = unsignedByteToInt(value); //pixels[loc] = (value + Byte.MIN_VALUE) << 24; } @Override public void setPixelBoolean(int loc, boolean value) { pixels[loc]= booleanToInt(value); //pixels[loc] = value ? Integer.MAX_VALUE : 0; } @Override public boolean equals(Image im) { if(im==null || !(im instanceof IntegerImage)) return false; if(!haveSameDimensions(this,im)) return false; int size = size(); for(int i = 0; i < size; i++) if(im.getPixelInt(i) != getPixelInt(i)) return false; return true; } @Override public void setPixel(Image input, int x1, int y1, int z1, int t1, int b1, int x2, int y2, int z2, int t2, int b2) { this.setPixelInt(x1, y1, z1, t1, b1, input .getPixelInt(x2, y2, z2, t2, b2)); } /** * Computes the minimum value of the image * @return * the integer representation of the minimum */ public int minimum(){ int val = Integer.MAX_VALUE; for (int p = 0; p < size(); p++) if (getPixelInt(p) < val) val = getPixelInt(p); return val; } /** * Computes the maximum value of the image * @return * the integer representation of the maximum */ public int maximum(){ int val = Integer.MIN_VALUE; for (int p = 0; p < size(); p++) if (getPixelInt(p) > val) val = getPixelInt(p); return val; } /** * Computes the minimum value of the image in the specified band * @param band * Band. * @return * the integer representation of the minimum in the specified band */ public int minimum(int band){ int val = Integer.MAX_VALUE; for (int p = band; p < size(); p+=this.getBDim()) if (getPixelInt(p) < val) val = getPixelInt(p); return val; } /** * Computes the maximum value of the image in the specified band * @param band * Band. * @return * the integer representation of the maximum in the specified band */ public int maximum(int band){ int val = Integer.MIN_VALUE; for (int p = band; p < size(); p+=this.getBDim()) if (getPixelInt(p) > val) val = getPixelInt(p); return val; } /** * Regroup the labels values to avoid unused label values * in label values range. * * Do not modified label 0 due to its special use in certain algorithm */ public void regroupLabels() { int nbLabels=this.maximumInt()+1; boolean[] isLabelUsed = new boolean[nbLabels]; Arrays.fill(isLabelUsed, false); for(int i=1;i<this.size();i++) { isLabelUsed[this.getPixelInt(i)]=true; } int lut[] = new int[nbLabels]; lut[0]=0; int currentLabel=1; for(int i=1;i<nbLabels;i++) { if(isLabelUsed[i]) lut[i]=currentLabel++; } for(int i=1;i<this.size();i++) { this.setPixelInt(i,lut[this.getPixelInt(i)]); } } /** * Computes the number of different labels used in the image */ public int getNumberOfUsedLabels() { boolean[] isLabelUsed = new boolean[this.maximumInt()+1]; Arrays.fill(isLabelUsed, false); if(this.getMask()==null||this.getMask().isEmpty()) { for(int i=0;i<this.size();i++) { isLabelUsed[this.getPixelInt(i)]=true; } } else { for(int i=0;i<this.size();i++) { if(this.isPresent(i)) isLabelUsed[this.getPixelInt(i)]=true; } } int numberOfUsedLabels=0; for(int i=0;i<isLabelUsed.length;i++) { if(isLabelUsed[i]) numberOfUsedLabels++; } return numberOfUsedLabels; } /** * Return an arraylist of used labels (multiband not managed) */ public ArrayList<Integer> getUsedLabels() { ArrayList<Integer> usedLabels = new ArrayList<Integer>(); for(int i=0;i<this.size();i++) { if(!usedLabels.contains(this.getPixelInt(i))) { usedLabels.add(this.getPixelInt(i)); } } return usedLabels; } @Override public boolean maximumBoolean() { return intToBoolean(this.maximum()); } @Override public int maximumByte() { return intToUnsignedByte(this.maximum()); } @Override public double maximumDouble() { return intToDouble(this.maximum()); } @Override public double maximumDouble(int band) { return intToDouble(this.maximum(band)); } @Override public double maximumDoubleIgnoreNonRealValues(int band) { return intToDouble(this.maximum(band)); } @Override public int maximumInt() { return this.maximum(); } @Override public boolean minimumBoolean() { return intToBoolean(this.minimum()); } @Override public int minimumByte() { return intToUnsignedByte(this.minimum()); } @Override public double minimumDouble() { return intToDouble(this.minimum()); } @Override public double minimumDouble(int band) { return intToDouble(this.minimum(band)); } @Override public double minimumDoubleIgnoreNonRealValues(int band) { return intToDouble(this.minimum(band)); } @Override public int minimumInt() { return this.minimum(); } /** * Gets the value of the pixels in the given location as a vector across all * channels * * @param x * the horizontal index * @param y * the vertical index * @param t * time position of the desired pixel * @return the value of the pixels in the given location as a vector across * all channels * * TODO : correct the bug or not ? possible side-effect ... */ public final int[] getVectorPixelByte(int index) { int[] vector = new int[bdim]; for (int b = 0; b < bdim; b++) vector[b] = pixels[index++]; return vector; } /** * Gets the value of the pixels in the given location as a vector across all * channels * * @param x * the horizontal index * @param y * the vertical index * @param t * time position of the desired pixel * @return the value of the pixels in the given location as a vector across * all channels */ public int[] getVectorPixelInt(int index) { int[] vector = new int[bdim]; for (int b = 0; b < bdim; b++) vector[b] = pixels[index++]; return vector; } }