/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.image.interpolation;
import java.awt.Rectangle;
import java.util.Arrays;
import org.geotoolkit.image.iterator.PixelIterator;
/**
* <p>Define standard interpolation.<br/><br/>
*
* Regardless interpolation type, each interpolation is computing from sample center.</p>
*
* @author Rémi Maréchal (Geomatys).
*/
public abstract class Interpolation {
/**
* Current {@code PixelIterator} which is interpolate.
*/
protected final PixelIterator pixelIterator;
/**
* Number of bands from object that iterate.
*/
protected final int numBands;
/**
* Boundary from object that iterate.
*/
protected final Rectangle boundary;
/**
* last search of min max value area.
*/
protected Rectangle precMinMax;
/**
* <p>Double table which contain minimum and maximum value, and x, y associate coordinates for each image band.</p>
*/
protected double[] minMax;
/**
* Table to keep all pixels values used to interpolate.
*/
protected final double[] data;
/**
* Lower corner X coordinates from interpolation area.
*/
protected int minX;
/**
* Lower corner Y coordinates from interpolation area.
*/
protected int minY;
/**
* Side of area interpolation.
*/
protected final int windowSide;
/**
* Interpolation results table.
*/
protected final double[] result;
/**
* Contains value use when pixel transformation is out of source image boundary.
*/
protected final double[] fillValue;
/**
* Define comportement of the destination image border.
*/
protected final ResampleBorderComportement borderChoice;
/**
* Define boundary which accept extrapolation.
*/
protected final double bminX, bminY, bmaxX, bmaxY;
/**
* Define boundary which don't accept extrapolation.
*/
protected final int boundMinX, boundMinY, boundMaxX, boundMaxY;
/**
* Build an Interpolation object initialize by the given parameter.
*
* @param pixelIterator {@link PixelIterator} which travel source image use to interpolate.
* @param windowSize define width and height from needed area to interpolate value in function of interpolation instance.
* @param borderChoice define comportement of the destination image border.
* @param fillValue contains value use when pixel transformation is out of source image boundary.
* @see BilinearInterpolation#BilinearInterpolation(org.geotoolkit.image.iterator.PixelIterator)
* @see BiCubicInterpolation#BiCubicInterpolation(org.geotoolkit.image.iterator.PixelIterator)
* @see LanczosInterpolation#LanczosInterpolation(org.geotoolkit.image.iterator.PixelIterator, int)
*/
public Interpolation(PixelIterator pixelIterator, int windowSize, ResampleBorderComportement borderChoice, double[] fillValue) {
this.pixelIterator = pixelIterator;
this.numBands = pixelIterator.getNumBands();
this.boundary = pixelIterator.getBoundary(false);
if (windowSize > boundary.width || windowSize > boundary.height)
throw new IllegalArgumentException("windowSide argument is more "
+ "larger than iterate object boundary side. boundary = "
+boundary+" windowSide = "+windowSize);
bminX = boundary.x - 0.5;
bminY = boundary.y - 0.5;
bmaxX = boundary.x + boundary.width - 0.5;
bmaxY = boundary.y + boundary.height - 0.5;
boundMinX = boundary.x;
boundMinY = boundary.y;
boundMaxX = boundary.x + boundary.width - 1;
boundMaxY = boundary.y + boundary.height - 1;
this.minMax = null;
this.windowSide = windowSize;
this.data = new double[windowSize * windowSize * numBands];
result = new double[numBands];
this.borderChoice = borderChoice;
if (fillValue == null) {
this.fillValue = new double[numBands];
Arrays.fill(this.fillValue, Double.NaN);
} else {
this.fillValue = fillValue;
}
}
public Interpolation(Interpolation source) {
this.pixelIterator = source.pixelIterator;
this.numBands = source.getNumBands();
this.boundary = source.getBoundary();
bminX = boundary.x - 0.5;
bminY = boundary.y - 0.5;
bmaxX = boundary.x + boundary.width - 0.5;
bmaxY = boundary.y + boundary.height - 0.5;
boundMinX = boundary.x;
boundMinY = boundary.y;
boundMaxX = boundary.x + boundary.width - 1;
boundMaxY = boundary.y + boundary.height - 1;
this.minMax = source.minMax;
this.windowSide = source.windowSide;
this.data = new double[windowSide * windowSide * numBands];
result = new double[numBands];
this.fillValue = source.fillValue;
this.borderChoice = source.borderChoice;
}
/**
* Return interpolate value from x, y pixel coordinate.
*
* @param x pixel x coordinate.
* @param y pixel y coordinate.
* @param band index of band where sample are interpolate.
* @return interpolate value from x, y pixel coordinate.
*/
public abstract double interpolate(double x, double y, int band);
/**
* Returns all pixel samples from interpolation at (x, y) sources coordinates.
* @param x pixel x coordinate.
* @param y pixel y coordinate.
* @return interpolate value from x, y pixel coordinate.
*/
public abstract double[] interpolate(double x, double y);
/**
* <p>Find minimum and maximum pixels values for each band.<br/>
* Moreover double table result has length equal to 6 * band number.<br/><br/>
* <var>min<sub>0</sub></var> : min from band 0.<br/>
* <var>minX<sub>0</sub></var> : x coordinate from min value from band 0.<br/>
* <var>minY<sub>0</sub></var> : y coordinate from min value from band 0.<br/>
* <var>max<sub>0</sub></var> : max from band 0.<br/>
* <var>maxX<sub>0</sub></var> : x coordinate from max value from band 0.<br/>
* <var>maxY<sub>0</sub></var> : y coordinate from max value from band 0.<br/>
* <var>min<sub>n</sub></var> : min from nth band.<br/>
* <var>minX<sub>n</sub></var> : x coordinate from min value from nth band.<br/>
* <var>minY<sub>n</sub></var> : y coordinate from min value from nth band.<br/>
* <var>max<sub>n</sub></var> : max from nth band.<br/>
* <var>maxX<sub>n</sub></var> : x coordinate from max value from nth band.<br/>
* <var>maxY<sub>n</sub></var> : y coordinate from max value from nth band.<br/><br/>
* Table is organize like this : <br/>
* [<var>min<sub>0</sub></var>, <var>minX<sub>0</sub></var>, <var>minY<sub>0</sub></var>,
* <var>max<sub>0</sub></var>, <var>maxX<sub>0</sub></var>, <var>maxY<sub>0</sub></var>
* ...
* <var>min<sub>n</sub></var>, <var>minX<sub>n</sub></var>, <var>minY<sub>n</sub></var>,
* <var>max<sub>n</sub></var>, <var>maxX<sub>n</sub></var>, <var>maxY<sub>n</sub></var>]<br/><br/>
*
* If Rectangle area parameter is {@code null} method will search minimum
* and maximum on all iterate object.</p>
*
* Note : This code is adapted for BiLinear and Neighbor interpolation.
* About another interpolation min and max values method isn't implemented yet.
*
* @param area area within search min and max values.
* @return double array witch represent minimum and maximum pixels values for each band.
*/
public double[] getMinMaxValue(Rectangle area) {
if (minMax != null) {
if ((area == null && precMinMax.equals(boundary))
|| area.equals(precMinMax)) return minMax;
}
//-- compute minMax values
minMax = new double[6 * numBands];
//-- initialize min and max value for each band
for (int band = 0;band<numBands; band++) {
final int minBandOrdinate = 6 * band;
//-- min value, x, y coordinates
minMax[minBandOrdinate] = Double.POSITIVE_INFINITY;
//-- max value, x, y coordinates
minMax[minBandOrdinate + 3] = Double.NEGATIVE_INFINITY;
}
/*
* If area is null iterate on all image area.
*/
if (area == null) {
int band = 0;
double value;
while(pixelIterator.next()) {
value = pixelIterator.getSampleDouble();
final int minBandOrdinate = 6 * band;
if (value < minMax[minBandOrdinate]) {
//-- min value, x, y coordinates
minMax[minBandOrdinate] = value;
minMax[minBandOrdinate + 1] = pixelIterator.getX();
minMax[minBandOrdinate + 2] = pixelIterator.getY();
}
if (value > minMax[minBandOrdinate + 3]) {
//-- max value, x, y coordinates
minMax[minBandOrdinate + 3] = value;
minMax[minBandOrdinate + 4] = pixelIterator.getX();
minMax[minBandOrdinate + 5] = pixelIterator.getY();
}
if (++band >= numBands) {
band = 0;
}
}
} else if (!getBoundary().contains(area)) {
throw new IllegalArgumentException("impossible to define min and max in area out of Iterate object boundary");
} else {
double value;
final int maxAreaX = area.x + area.width;
final int maxAreaY = area.y + area.height;
for (int y = area.y; y < maxAreaY; y++) {
for (int x = area.x; x < maxAreaX; x++) {
/*
* Call moveTo at each pixel coordinates because we don't know Iterator implementation.
* Iterate row by row or raster by raster has different comportements.
*/
pixelIterator.moveTo(x, y, 0);
for (int band = 0; band < numBands; band++) {
value = pixelIterator.getSampleDouble();
final int minBandOrdinate = 6 * band;
if (value < minMax[minBandOrdinate]) {
//-- min value, x, y coordinates
minMax[minBandOrdinate] = value;
minMax[minBandOrdinate + 1] = x;
minMax[minBandOrdinate + 2] = y;
}
if (value > minMax[minBandOrdinate + 3]) {
//-- max value, x, y coordinates
minMax[minBandOrdinate + 3] = value;
minMax[minBandOrdinate + 4] = x;
minMax[minBandOrdinate + 5] = y;
}
pixelIterator.next();
}
}
}
}
precMinMax = (area == null) ? boundary : area;
return minMax;
}
/**
* Verify coordinates are within iterate area boundary.
*
* @param x pixel x coordinate.
* @param y pixel y coordinate.
* @return {@code true} if value should be interpolate at the given coordinates else {@code false}.
* @throws IllegalArgumentException if pixel coordinates are out of iterate area boundary.
*/
protected boolean checkInterpolate(double x, double y) {
//-- accept extrapolation
if (borderChoice.equals(ResampleBorderComportement.EXTRAPOLATION)) return true;
if (x < bminX || x > bmaxX || y < bminY || y > bmaxY) return false; //-- no interpolation available
// if (borderChoice.equals(ResampleBorderComportement.EXTRAPOLATION)) return true;
return (!(x < boundMinX || x > boundMaxX
|| y < boundMinY || y > boundMaxY));
}
/**
* Return appropriate interpolation minX and minY coordinates from x, y interpolate coordinates.
* MinX and minY represent lower corner coordinates from interpolation area.
*
* @param x pixel x coordinate.
* @param y pixel y coordinate.
* @throws IllegalArgumentException if there are necessary pixels out of boundary.
*/
protected void setInterpolateMin(double x, double y) {
final int boundW = boundary.width;
final int boundH = boundary.height;
final int bx = boundary.x;
final int by = boundary.y;
minX = (int) x;
minY = (int) y;
//-- Adjust truncation.
if (x < minX) minX--;
if (y < minY) minY--;
//-- Adjust area interpolation on x, y center.
minX -= (windowSide >>> 1) - 1;
minY -= (windowSide >>> 1) - 1;
//-- Adjust area from lower corner.
minX = Math.max(minX, bx);
minY = Math.max(minY, by);
//-- Adjust area from upper corner.
final int maxDiffX = minX + windowSide - (bx + boundW);
final int maxDiffY = minY + windowSide - (by + boundH);
if (maxDiffX > 0) minX -= maxDiffX;
if (maxDiffY > 0) minY -= maxDiffY;
}
/**
* Returns {@code Rectangle} which is Image or Raster boundary within this Interpolator.
*
* @return {@code Rectangle} which is Image or Raster boundary within this Interpolator.
* @see Resample#getSourcePixelValue(double, double)
*/
Rectangle getBoundary() {
return boundary;
}
/**
* Return number of bands from object that iterate.
*
* @return number of bands from object that iterate.
* @see Resample#getSourcePixelValue(double, double)
*/
public int getNumBands() {
return numBands;
}
/**
* <p>Return Interpolation object.<br/><br/>
*
* Note : if lanczos interpolation doesn't choose lanczosWindow parameter has no impact.</p>
*
* @param pixelIterator Iterator which iterate to compute interpolation.
* @param interpolationCase case of interpolation.
* @param lanczosWindow only use about Lanczos interpolation.
* @return interpolation asked by caller.
* @see LanczosInterpolation#LanczosInterpolation(org.geotoolkit.image.iterator.PixelIterator, int)
*/
public static Interpolation create(PixelIterator pixelIterator, InterpolationCase interpolationCase, int lanczosWindow) {
return create(pixelIterator, interpolationCase, lanczosWindow, ResampleBorderComportement.FILL_VALUE, null);
}
/**
* <p>Return Interpolation object.<br/><br/>
*
* Note : if lanczos interpolation doesn't choose lanczosWindow parameter has no impact.</p>
*
* @param pixelIterator Iterator which iterate to compute interpolation.
* @param interpolationCase case of interpolation.
* @param lanczosWindow only use about Lanczos interpolation.
* @param rbc comportement of the destination image border.
* @param fillValue contains value use when pixel transformation is out of source image boundary.
* @return interpolation asked by caller.
* @see LanczosInterpolation#LanczosInterpolation(org.geotoolkit.image.iterator.PixelIterator, int)
*/
public static Interpolation create(PixelIterator pixelIterator, InterpolationCase interpolationCase, int lanczosWindow,
ResampleBorderComportement rbc, double[] fillValue) {
switch (interpolationCase) {
case NEIGHBOR : return new NeighborInterpolation(pixelIterator, fillValue);
case BILINEAR : return new BilinearInterpolation(pixelIterator, rbc, fillValue);
case BICUBIC : return new BiCubicInterpolation1(pixelIterator, rbc, fillValue);
case BICUBIC2 : return new BiCubicInterpolation2(pixelIterator, rbc, fillValue);
case LANCZOS : return new LanczosInterpolation(pixelIterator, lanczosWindow, rbc, fillValue);
default : throw new IllegalArgumentException("interpolation not supported yet");
}
}
}