/*
* This file is part of the JFeatureLib project: https://github.com/locked-fg/JFeatureLib
* JFeatureLib is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* JFeatureLib 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JFeatureLib; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* You are kindly asked to refer to the papers of the according authors which
* should be mentioned in the Javadocs of the respective classes as well as the
* JFeatureLib project itself.
*
* Hints how to cite the projects can be found at
* https://github.com/locked-fg/JFeatureLib/wiki/Citation
*/
package de.lmu.ifi.dbs.jfeaturelib.features.surf;
import ij.IJ;
import ij.process.*;
// TODO detach from IJ, move IJ-related code into IJFacade
public class IntegralImage {
/**
* Should be set via setData() method.
*/
private float[][] data;
/**
* Sets internal array to the argument
* <code>a</code> and updates attributes width, height, maxX, maxY.
*/
private void setData(float[][] a) {
data = a;
width = data.length;
height = data[0].length;
maxX = width - 1;
maxY = height - 1;
}
private int width;
private int height;
/**
* Max valid X coordinate.
*/
private int maxX;
/**
* Max valid Y coordinate.
*/
private int maxY;
public float get(int x, int y) {
return data[x][y];
}
// set() is not needed
// public void set(int x, int y, float val) {
// data[x][y] = val;
// }
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getMaxX() {
return maxX;
}
public int getMaxY() {
return maxY;
}
// TODO: constructors for byte[] and float[] input images?
/**
* Creates the integral image of the source image. The order of coordinates
* in the integral image is [x][y]. <p> The
* <code>integral image</code> is an image where pixel values are sums of
* pixel values above and to the left of the actual pixel (inclusive) from
* the source image.
*/
public IntegralImage(ImageProcessor src) { // TODO: remove this constructor in favor of IntegralImage(ImageProcessor, boolean)!!!
// Get the source image as a new float[][] grayscale array
// using the defautl ImageJ algorithm.
// NB: in OpenSURF C# version following is used to convert from RGB to
// grayscale:
// luminance = (0.3 R + 0.59 G + 0.11 B) / 255.
// ImageJ:
// Weighting factors used by getPixelValue(), getHistogram() and
// convertToByte().
// Enable "Weighted RGB Conversion" in <i>Edit/Options/Conversions</i>
// to use 0.299, 0.587 and 0.114.
// private static double rWeight=1d/3d, gWeight=1d/3d, bWeight=1d/3d;
// TODO: Call ColorProcessor.setWeightingFactors() ?
// TODO: implement as int[][] or long[][]? Or as int[] or long[]?
// NB: OpenSURF does cvConvertScale( gray8, gray32, 1.0 / 255.0, 0 );
// at converting to grayscale!
// Initialize the instance variables
setData(src.convertToByte(false).getFloatArray()); // [width][height]
// [x][y]
convertInternalBufferToIntegralImage();
}
/**
* Compute the integral image.
*/
private void convertInternalBufferToIntegralImage() {
float rowSum = 0;
// first row:
for (int x = 0; x < width; x++) {
rowSum += data[x][0];
data[x][0] = rowSum;
}
// the rest:
for (int y = 1; y < height; y++) {
rowSum = 0;
for (int x = 0; x < width; x++) {
rowSum += data[x][y];
data[x][y] = rowSum + data[x][y - 1];
}
}
}
public IntegralImage(ImageProcessor src, boolean weightedAndNormalizedConversion) {
// TODO: make weightedAndNormalizedConversion the default and remove other constructors!
int width = src.getWidth();
int height = src.getHeight();
float[][] a = new float[width][height];
float val, min = Float.MAX_VALUE, max = Float.MIN_VALUE;
int i, x, y;
float[] col;
// Convert to float and compute min and max values
if (src instanceof ByteProcessor || src instanceof ShortProcessor || src instanceof FloatProcessor) {
for (i = 0, y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
val = src.getf(i++);
a[x][y] = val;
if (val < min) {
min = val;
} else if (val > max) {
max = val;
}
}
}
} else if (src instanceof ColorProcessor) { // weighted conversion
int intVal, r, g, b;
float rw = 0.299f, gw = 0.587f, bw = 0.114f;
for (i = 0, y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
intVal = src.get(i++);
r = (intVal & 0xff0000) >> 16;
g = (intVal & 0xff00) >> 8;
b = intVal & 0xff;
val = r * rw + g * gw + b * bw;
a[x][y] = val;
if (val < min) {
min = val;
} else if (val > max) {
max = val;
}
}
}
} else { // Should never happen.
IJ.error("SURF: IntegralImage", "Unknown image type.\nCannot proceed.");
return;
}
// Normalize values (i.e. scale max-min range to 0..1 range)
float scale = 1f / (max - min);
for (i = 0, x = 0; x < width; x++) {
col = a[x];
for (y = 0; y < height; y++) {
val = col[y] - min;
if (val < 0) {
val = 0;
}
val *= scale;
if (val > 1) {
val = 1;
}
col[y] = val;
}
}
setData(a);
convertInternalBufferToIntegralImage();
}
/**
* Computes the sum of pixels in an integral image
* <code>img</code> within the rectangle specified by the top-left start
* coordinate (inclusive) and size.<br>
*/
float area(int x1, int y1, int rectWidth, int rectHeight) {
x1--;
y1--; // A +--------+ B
int x2 = x1 + rectWidth; // | | A(x1,y1)
int y2 = y1 + rectHeight; // C +--------+ D
// bounds check
if (x1 > maxX) {
x1 = maxX;
}
if (y1 > maxY) {
y1 = maxY;
}
if (x2 > maxX) {
x2 = maxX;
}
if (y2 > maxY) {
y2 = maxY;
}
float A = (x1 < 0 || y1 < 0) ? 0 : data[x1][y1];
float B = (x2 < 0 || y1 < 0) ? 0 : data[x2][y1];
float C = (x1 < 0 || y2 < 0) ? 0 : data[x1][y2];
float D = (x2 < 0 || y2 < 0) ? 0 : data[x2][y2];
return D - B - C + A;
}
/**
* A speed optimized version of {@link #area(FloatProcessor, int, int, int, int)}
* without bounds check (for 0 < x1 < width and 0 < y1 < height).
*/
float area2(int x1, int y1, int rectWidth, int rectHeight) {
x1--;
y1--; // A +--------+ B
int x2 = x1 + rectWidth; // | | A(x1,y1)
int y2 = y1 + rectHeight; // C +--------+ D
return data[x2][y2] - data[x2][y1] - data[x1][y2] + data[x1][y1]; // D - B - C + A
}
}