package edu.oregonstate.cartography.grid.operators;
import edu.oregonstate.cartography.grid.Grid;
import edu.oregonstate.cartography.grid.LaplacianPyramid;
// FIXME filter is square instead of circular
// FIXME should use Gaussian bell curve for weighting values
// FIXME does not work properly with NaN values
// FIXME does not work with large filter size and small grid
// FIXME document code
/**
*
* @author Bernhard Jenny, Oregon State University
*/
public final class GridStandardDeviationOperator extends ThreadedGridOperator {
private static final int FILTER_SIZE_SCALE = 16;
private int levels = 3;
private LaplacianPyramid laplacianPyramid;
private GridStandardDeviationOperator() {
}
public GridStandardDeviationOperator(int levels, LaplacianPyramid laplacianPyramid) {
this.levels = levels;
this.laplacianPyramid = laplacianPyramid;
}
private int filterSize() {
return levels * FILTER_SIZE_SCALE + 1;
}
@Override
public String getName() {
return "Local Standard Deviation Estimation";
}
@Override
protected void operate(Grid src, Grid dst, int startRow, int endRow) {
if (src == null) {
throw new IllegalArgumentException();
}
// FIXME make sure filterSize is odd number
final int filterSize = filterSize();
final int halfFilterSize = filterSize / 2;
final int cols = src.getCols();
final int rows = src.getRows();
float[][] srcGrid = src.getGrid();
float[][] dstGrid = dst.getGrid();
// extract high-pass band from Laplacian pyramid
float[] weights = laplacianPyramid.createConstantWeights(0);
for (int i = 0; i < Math.min(levels, weights.length); i++) {
weights[i] = 1;
}
Grid highPassGrid = laplacianPyramid.sumLevels(weights, true);
// FIXME the following code does not work if the filter size is larger
// than the grid
// top rows
for (int row = startRow; row < halfFilterSize; row++) {
for (int col = 0; col < cols; col++) {
operateBorder(src, dst, col, row, highPassGrid);
}
}
// bottom rows
for (int row = rows - halfFilterSize; row < endRow; row++) {
for (int col = 0; col < cols; col++) {
operateBorder(src, dst, col, row, highPassGrid);
}
}
startRow = Math.max(halfFilterSize, startRow);
endRow = Math.min(src.getRows() - halfFilterSize, endRow);
// left columns
for (int col = 0; col < halfFilterSize; col++) {
for (int row = startRow; row < endRow; row++) {
operateBorder(src, dst, col, row, highPassGrid);
}
}
// right columns
for (int col = cols - halfFilterSize; col < cols; col++) {
for (int row = startRow; row < endRow; row++) {
operateBorder(src, dst, col, row, highPassGrid);
}
}
// interior of grid
// FIXME adjust npts to number of NaNs
final float npts = filterSize * filterSize;
for (int row = startRow; row < endRow; row++) {
float[] dstRow = dstGrid[row];
for (int col = halfFilterSize; col < cols - halfFilterSize; col++) {
float sqDif = 0;
for (int r = row - halfFilterSize; r <= row + halfFilterSize; r++) {
float[] srcRow = srcGrid[r];
for (int c = col - halfFilterSize; c <= col + halfFilterSize; c++) {
// FIXME avoid function call
float dif = highPassGrid.getValue(c, r);
// FIXME test for NaN
// FIXME apply weighting with Gaussian bell
sqDif += dif * dif;
}
}
float std = (float) Math.sqrt(sqDif / npts);
dstRow[col] = std;
}
}
}
private void operateBorder(Grid src, Grid dst, int col, int row, Grid highPassGrid) {
// make sure filterSize is odd number
final int filterSize = filterSize();
final int halfFilterSize = filterSize / 2;
float[][] srcGrid = src.getGrid();
float[][] dstGrid = dst.getGrid();
final int cols = src.getCols();
final int rows = src.getRows();
// FIXME adjust npts for NaN values
int npts = filterSize * filterSize;
float sqDif = 0;
for (int r = row - halfFilterSize; r <= row + halfFilterSize; r++) {
if (r > 0 && r < rows) {
float[] srcRow = srcGrid[r];
for (int c = col - halfFilterSize; c <= col + halfFilterSize; c++) {
if (c > 0 && c < cols) {
final float v = srcRow[c];
if (!Float.isNaN(v)) {
float dif = highPassGrid.getValue(c, r);
sqDif += dif * dif;
}
}
}
}
}
float std = (float) Math.sqrt(sqDif / npts);
dstGrid[row][col] = std;
}
}