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; } }