/*
* ESO Archive
*
* $Id: MinMaxOpImage.java,v 1.2 2009/02/20 23:10:11 abrighto Exp $
*
* who when what
* -------------- ---------- ----------------------------------------
* Allan Brighton 1999/05/03 Created
*/
package jsky.image.operator;
import java.awt.geom.Rectangle2D;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import javax.media.jai.ROI;
import javax.media.jai.StatisticsOpImage;
/**
* This operation is used to get the min and max pixel values, like the JAI extrema
* operation, except that it ignores pixels with a given bad pixel value and is designed
* (currently) only to work with single banded images. The bad pixel value is specified
* as a double. If the value is Double.NaN, it is ignored. Also, any pixel values
* that are NaNs are ignored.
*
* MinMaxOpImage is an extension of StatisticsOpImage that takes
* a region of interest (ROI), two integer parameters (xPeriod and yPeriod),
* a bad pixel value (ignore), and one source image and calculates image min
* and max values, ignoring any bad pixels.
*/
class MinMaxOpImage extends StatisticsOpImage {
// Note: about the base class, Daniel Rice wrote:
// xStart, yStart, xPeriod, and yPeriod define a uniform but sparse set of
// sample points. You might want to accumulate statistics on several regions
// of interest, but each time using the same subgrid for consistency.
// maxWidth and maxHeight don't affect the results, they just set up a contract
// with the future caller of accumulateStatictics which may be of use to the
// implementer of a particular statistics op who need to allocate temporary
// storage.
/** value of the pixels to ignore */
private double ignore;
/**
* The statistics operation names
*/
private static final String[] opNames = {
"minmax"
};
/**
* Constructs an MinMaxOpImage.
*
* @param source a RenderedImage.
* @param roi The region of interest
* @param xPeriod skip this many pixels in the X direction
* @param yPeriod skip this many pixels in the Y direction
* @param ignore ignore any pixels with this value
*/
public MinMaxOpImage(RenderedImage source,
ROI roi, Integer xPeriod, Integer yPeriod,
Double ignore) {
// XXX JAI 1.0.2 super(source, roi, 0, 0, xPeriod.intValue(), yPeriod.intValue(), source.getWidth(), source.getHeight());
super(source, roi, 0, 0, xPeriod, yPeriod);
this.ignore = ignore;
}
/**
* Update the min and max values for the specified region, using the current parameters.
*
* @param name the name of the statistic to be gathered.
*
* @param source a Raster containing source pixels.
* The dimensions of the Raster will not exceed maxWidth x maxHeight.
*
* @param ar an array of two doubles to hold the min and max values (created by createStatistics())
*/
protected void accumulateStatistics(String name, Raster source, Object ar) {
double[] stats = (double[]) ar;
DataBuffer dbuf = source.getDataBuffer();
// clip the region to the intersection of the ROI with the source tile
Rectangle2D rect = roi.getBounds().createIntersection(source.getBounds());
//System.out.println("XXX accumulateStatistics: ROI = " + roi.getBounds() + ", source = " + source.getBounds() + ", intersect = " + rect);
int x0 = Math.max((int) rect.getX() - source.getMinX(), 0);
int y0 = Math.max((int) rect.getY() - source.getMinY(), 0);
int x1 = x0 + (int) rect.getWidth() - 1;
int y1 = y0 + (int) rect.getHeight() - 1;
int w = source.getWidth();
// int h = source.getHeight();
// ignore pixels from the border
if (xPeriod < width / 2 && yPeriod < height / 2) {
x0 += xPeriod;
y0 += yPeriod;
x1 -= xPeriod;
y1 -= yPeriod;
}
if (x0 >= x1 || y0 >= y1) return;
// XXX for now, only do the default bank. (How to treat multiple banks?)
switch (dbuf.getDataType()) {
case DataBuffer.TYPE_BYTE:
{
DataBufferByte dataBuffer = (DataBufferByte) source.getDataBuffer();
byte[] data = dataBuffer.getData();
short ignore = (short) this.ignore;
getMinMaxByte(data, ignore, x0, y0, x1, y1, w, stats);
}
break;
case DataBuffer.TYPE_SHORT:
{
DataBufferShort dataBuffer = (DataBufferShort) source.getDataBuffer();
short[] data = dataBuffer.getData();
short ignore = (short) this.ignore;
getMinMaxShort(data, ignore, x0, y0, x1, y1, w, stats);
}
break;
case DataBuffer.TYPE_USHORT:
{
DataBufferUShort dataBuffer = (DataBufferUShort) source.getDataBuffer();
short[] data = dataBuffer.getData();
int ignore = (int) this.ignore;
getMinMaxUShort(data, ignore, x0, y0, x1, y1, w, stats);
}
break;
case DataBuffer.TYPE_INT:
{
DataBufferInt dataBuffer = (DataBufferInt) source.getDataBuffer();
int[] data = dataBuffer.getData();
int ignore = (int) this.ignore;
getMinMaxInt(data, ignore, x0, y0, x1, y1, w, stats);
}
break;
case DataBuffer.TYPE_FLOAT:
case DataBuffer.TYPE_DOUBLE:
{
getMinMax(source.getDataBuffer(), ignore, x0, y0, x1, y1, w, stats);
}
break;
default:
throw new IllegalArgumentException("MinMax not implemented for this data type");
}
}
/**
* Get the min and max pixel values in the given region and write
* them to the given array (Byte version).
*
* @param data The image data.
* @param ignore The value of the pixels to ignore
* @param w The width of the source image.
* @param stats array to hold the results.
*/
void getMinMaxByte(byte[] data, short ignore, int x0, int y0, int x1, int y1, int w,
double[] stats) {
short min, max;
if (!Double.isNaN(stats[0])) {
min = (short) stats[0];
max = (short) stats[1];
} else {
min = data[0];
// check for ignores
if (min == ignore) {
done:
for (int i = x0; i <= x1; i += xPeriod) {
for (int j = y0; j <= y1; j += yPeriod) {
min = data[j * w + i];
if (min == ignore)
continue;
break done;
}
}
}
if (min == ignore) {
min = 0;
}
max = min;
}
for (int i = x0; i <= x1; i += xPeriod) {
for (int j = y0; j <= y1; j += yPeriod) {
byte val = data[j * w + i];
if (val == ignore)
continue;
if (val < min)
min = val;
else if (val > max) {
max = val;
}
}
}
stats[0] = min;
stats[1] = max;
}
/**
* Get the min and max pixel values in the given region and write
* them to the given array (Short version).
*
* @param data The image data.
* @param ignore The value of the pixels to ignore
* @param x0 The coordinates of the area to examine.
* @param y0 The coordinates of the area to examine.
* @param x1 The coordinates of the area to examine.
* @param y1 The coordinates of the area to examine.
* @param w The width of the source image.
* @param stats array to hold the results.
*/
void getMinMaxShort(short[] data, short ignore, int x0, int y0, int x1, int y1, int w,
double[] stats) {
short min, max;
if (!Double.isNaN(stats[0])) {
min = (short) stats[0];
max = (short) stats[1];
} else {
min = data[0];
// check for ignores
if (min == ignore) {
done:
for (int i = x0; i <= x1; i += xPeriod) {
for (int j = y0; j <= y1; j += yPeriod) {
min = data[j * w + i];
if (min == ignore)
continue;
break done;
}
}
}
if (min == ignore) {
min = 0;
}
max = min;
}
for (int i = x0; i <= x1; i += xPeriod) {
for (int j = y0; j <= y1; j += yPeriod) {
short val = data[j * w + i];
if (val == ignore)
continue;
if (val < min)
min = val;
else if (val > max) {
max = val;
}
}
}
stats[0] = min;
stats[1] = max;
}
/**
* Get the min and max pixel values in the given region and write
* them to the given array (UShort version).
*
* @param data The image data.
* @param ignore The value of the pixels to ignore
* @param x0 The coordinates of the area to examine.
* @param y0 The coordinates of the area to examine.
* @param x1 The coordinates of the area to examine.
* @param y1 The coordinates of the area to examine.
* @param w The width of the source image.
* @param stats array to hold the results.
*/
void getMinMaxUShort(short[] data, int ignore, int x0, int y0, int x1, int y1, int w,
double[] stats) {
int min, max;
if (!Double.isNaN(stats[0])) {
min = (int) stats[0];
max = (int) stats[1];
} else {
min = data[0] & 0xffff;
// check for ignores
if (min == ignore) {
done:
for (int i = x0; i <= x1; i += xPeriod) {
for (int j = y0; j <= y1; j += yPeriod) {
min = data[j * w + i];
if (min == ignore)
continue;
break done;
}
}
}
if (min == ignore) {
min = 0;
}
max = min;
}
for (int i = x0; i <= x1; i += xPeriod) {
for (int j = y0; j <= y1; j += yPeriod) {
int val = data[j * w + i] & 0xffff;
if (val == ignore)
continue;
if (val < min)
min = val;
else if (val > max) {
max = val;
}
}
}
stats[0] = min;
stats[1] = max;
}
/**
* Get the min and max pixel values in the given region and write
* them to the given array (Int version).
*
* @param data The image data.
* @param ignore The value of the pixels to ignore
* @param x0 The coordinates of the area to examine.
* @param y0 The coordinates of the area to examine.
* @param x1 The coordinates of the area to examine.
* @param y1 The coordinates of the area to examine.
* @param w The width of the source image.
* @param stats array to hold the results.
*/
void getMinMaxInt(int[] data, int ignore, int x0, int y0, int x1, int y1, int w,
double[] stats) {
int min, max;
if (!Double.isNaN(stats[0])) {
min = (int) stats[0];
max = (int) stats[1];
} else {
min = data[0];
// check for ignores
if (min == ignore) {
done:
for (int i = x0; i <= x1; i += xPeriod) {
for (int j = y0; j <= y1; j += yPeriod) {
min = data[j * w + i];
if (min == ignore)
continue;
break done;
}
}
}
if (min == ignore) {
min = 0;
}
max = min;
}
for (int i = x0; i <= x1; i += xPeriod) {
for (int j = y0; j <= y1; j += yPeriod) {
int val = data[j * w + i];
if (val == ignore)
continue;
if (val < min)
min = val;
else if (val > max) {
max = val;
}
}
}
stats[0] = min;
stats[1] = max;
}
/**
* Get the min and max pixel values in the given region and write
* them to the given array (Float/Double version).
*
* @param data The image data.
* @param ignore The value of the pixels to ignore
* @param x0 The coordinates of the area to examine.
* @param y0 The coordinates of the area to examine.
* @param x1 The coordinates of the area to examine.
* @param y1 The coordinates of the area to examine.
* @param w The width of the source image.
* @param stats array to hold the results.
*/
void getMinMax(DataBuffer data, double ignore,
int x0, int y0, int x1, int y1, int w, double[] stats) {
double min, max, mean;
if (!Double.isNaN(stats[0])) {
min = stats[0];
max = stats[1];
mean = stats[2];
} else {
min = data.getElemDouble(0);
// check for NaNs and ignores
if (Double.isNaN(min) || (min == ignore)) {
done:
for (int i = x0; i <= x1; i += xPeriod) {
for (int j = y0; j <= y1; j += yPeriod) {
min = data.getElemDouble(j * w + i);
if (Double.isNaN(min) || (min == ignore))
continue;
break done;
}
}
}
if (Double.isNaN(min) || (min == ignore)) {
min = 0.0;
}
max = mean = min;
}
double sum = 0;
int count = 0;
for (int i = x0; i <= x1; i += xPeriod) {
for (int j = y0; j <= y1; j += yPeriod) {
double val = data.getElemDouble(j * w + i);
if (Double.isNaN(val) || (val == ignore))
continue;
count++;
sum += val;
if (val < min)
min = val;
else if (val > max) {
max = val;
}
}
}
stats[0] = min;
stats[1] = max;
stats[2] = ((sum/count) + mean)/2; // calculate the running average
}
/**
* Returns an object that will be used to gather the named statistic.
*
* @param name the name of the statistic to be gathered.
*/
protected Object createStatistics(java.lang.String name) {
double[] ar = new double[3];
ar[0] = ar[1] = ar[2] = Double.NaN; // initial values are undefined
return ar;
}
/**
* Returns a list of names of statistics understood by this image.
*/
protected String[] getStatisticsNames() {
return opNames;
}
}