package edu.oregonstate.cartography.grid;
/**
*
* @author Bernhard Jenny, Institute of Cartography, ETH Zurich.
*/
public class Convolution5x5 {
/**
* kernel weights. A standard deviation of 1 would result in (9 elements):
* 0.0001 0.0044 0.054 0.242 0.3989 0.242 0.054 0.0044 0.0001
*/
private final float wa = 0.4f;
private final float wb = 0.25f;
private final float wc = 0.05f;
public Convolution5x5() {
}
public Grid convolveToHalfSize(Grid grid) {
final int rows = grid.getRows();
final int cols = grid.getCols();
if (cols < 4 || rows < 4) {
return null;
}
final int newCols = cols / 2 + cols % 2;
final int newRows = rows / 2 + rows % 2;
Grid convGrid = new Grid(newCols, newRows, grid.getCellSize() * 2);
convGrid.setWest(grid.getWest());
convGrid.setSouth(grid.getSouth());
// top and bottom rows
for (int c = 0; c < cols; c += 2) {
float g = this.convolveBorder(grid, c, 0);
convGrid.setValue(g, c / 2, 0);
g = this.convolveBorder(grid, c, rows - 1);
convGrid.setValue(g, c / 2, rows / 2 - 1 + rows % 2);
}
// left and right columns
for (int r = 0; r < rows; r += 2) {
float g = this.convolveBorder(grid, 0, r);
convGrid.setValue(g, 0, r / 2);
g = this.convolveBorder(grid, cols - 1, r);
convGrid.setValue(g, cols / 2 - 1 + cols % 2, r / 2);
}
// interior of grid
for (int r = 2; r < rows - 2; r += 2) {
for (int c = 2; c < cols - 2; c += 2) {
final float g = this.convolve(grid, c, r);
convGrid.setValue(g, c / 2, r / 2);
}
}
return convGrid;
}
public Grid convolve(Grid geoGrid) {
final int rows = geoGrid.getRows();
final int cols = geoGrid.getCols();
Grid convoluted = new Grid(cols, rows, geoGrid.getCellSize());
//convoluted.setWest(geoGrid.getWest());
//convoluted.setNorth(geoGrid.getNorth());
// top and bottom rows
for (int c = 0; c < cols; c++) {
convoluted.setValue(this.convolveBorder(geoGrid, c, 0), c, 0);
convoluted.setValue(this.convolveBorder(geoGrid, c, 1), c, 1);
convoluted.setValue(this.convolveBorder(geoGrid, c, rows - 1), c, rows - 1);
convoluted.setValue(this.convolveBorder(geoGrid, c, rows - 2), c, rows - 2);
}
// left and right columns
for (int r = 0; r < rows; r++) {
convoluted.setValue(this.convolveBorder(geoGrid, 0, r), 0, r);
convoluted.setValue(this.convolveBorder(geoGrid, 1, r), 1, r);
convoluted.setValue(this.convolveBorder(geoGrid, cols - 1, r), cols - 1, r);
convoluted.setValue(this.convolveBorder(geoGrid, cols - 2, r), cols - 2, r);
}
// interior of grid
for (int r = 2; r < rows - 2; r++) {
for (int c = 2; c < cols - 2; c++) {
convoluted.setValue(this.convolve(geoGrid, c, r), c, r);
}
}
return convoluted;
}
private float convolveBorder(Grid geoGrid, int col, int row) {
final int rows = geoGrid.getRows();
// mirror values
/*
final int r0 = row - 2 < 0 ? row + 2 : row - 2;
final int r1 = row - 1 < 0 ? row + 1 : row - 1;
final int r3 = row + 1 >= rows ? row - 1 : row + 1;
final int r4 = row + 2 >= rows ? row - 2 : row + 2;
*/
// use border values. First derivative is zero.
final int r0 = row - 2 < 0 ? 0 : row - 2;
final int r1 = row - 1 < 0 ? 0 : row - 1;
final int r3 = row + 1 >= rows ? rows - 1 : row + 1;
final int r4 = row + 2 >= rows ? rows - 1 : row + 2;
float v0 = convolveBorderRow(geoGrid, col, r0);
float v1 = convolveBorderRow(geoGrid, col, r1);
float v2 = convolveBorderRow(geoGrid, col, row);
float v3 = convolveBorderRow(geoGrid, col, r3);
float v4 = convolveBorderRow(geoGrid, col, r4);
final float res = wc * (v0 + v4) + wb * (v1 + v3) + wa * v2;
return Float.isNaN(res) ? convolveWithVoid(geoGrid, col, row) : res;
}
private float convolveBorderRow(Grid geoGrid, int col, int row) {
final int cols = geoGrid.getCols();
// mirror
/*
final int c0 = col - 2 < 0 ? col + 2 : col - 2;
final int c1 = col - 1 < 0 ? col + 1 : col - 1;
final int c3 = col + 1 >= cols ? col - 1 : col + 1;
final int c4 = col + 2 >= cols ? col - 2 : col + 2;
*/
final int c0 = col - 2 < 0 ? 0 : col - 2;
final int c1 = col - 1 < 0 ? 0 : col - 1;
final int c3 = col + 1 >= cols ? cols - 1 : col + 1;
final int c4 = col + 2 >= cols ? cols - 1 : col + 2;
final float v0 = geoGrid.getValue(c0, row);
final float v1 = geoGrid.getValue(c1, row);
final float v2 = geoGrid.getValue(col, row);
final float v3 = geoGrid.getValue(c3, row);
final float v4 = geoGrid.getValue(c4, row);
return wc * (v0 + v4) + wb * (v1 + v3) + wa * v2;
}
private float convolve(Grid geoGrid, int col, int row) {
final float v0 = convolveRow(geoGrid, col, row - 2);
final float v1 = convolveRow(geoGrid, col, row - 1);
final float v2 = convolveRow(geoGrid, col, row);
final float v3 = convolveRow(geoGrid, col, row + 1);
final float v4 = convolveRow(geoGrid, col, row + 2);
final float res = wc * (v0 + v4) + wb * (v1 + v3) + wa * v2;
return Float.isNaN(res) ? convolveWithVoid(geoGrid, col, row) : res;
}
private float convolveRow(Grid geoGrid, int col, int row) {
final float v0 = geoGrid.getValue(col - 2, row);
final float v1 = geoGrid.getValue(col - 1, row);
final float v2 = geoGrid.getValue(col, row);
final float v3 = geoGrid.getValue(col + 1, row);
final float v4 = geoGrid.getValue(col + 2, row);
return wc * (v0 + v4) + wb * (v1 + v3) + wa * v2;
}
private float convolveWithVoid(float v0, float v1, float v2, float v3, float v4) {
float totW = 0f;
float res = 0f;
if (!Float.isNaN(v0)) {
res = wc * v0;
totW = wc;
}
if (!Float.isNaN(v1)) {
res += wb * v1;
totW += wb;
}
if (!Float.isNaN(v2)) {
res += wa * v2;
totW += wa;
}
if (!Float.isNaN(v3)) {
res += wb * v3;
totW += wb;
}
if (!Float.isNaN(v4)) {
res += wc * v4;
totW += wc;
}
if (totW == 0) {
return Float.NaN;
}
final float scale = (2 * (wc + wb) + wa) / totW;
return res * scale;
}
private float convolveWithVoid(Grid geoGrid, int col, int row) {
final int rows = geoGrid.getRows();
final float v0 = convolveRowWithVoid(geoGrid, col, Math.max(0, row - 2));
final float v1 = convolveRowWithVoid(geoGrid, col, Math.max(0, row - 1));
final float v2 = convolveRowWithVoid(geoGrid, col, row);
final float v3 = convolveRowWithVoid(geoGrid, col, Math.min(rows - 1, row + 1));
final float v4 = convolveRowWithVoid(geoGrid, col, Math.min(rows - 1, row + 2));
return convolveWithVoid(v0, v1, v2, v3, v4);
}
private float convolveRowWithVoid(Grid geoGrid, int col, int row) {
final int cols = geoGrid.getCols();
final float v0 = geoGrid.getValue(Math.max(0, col - 2), row);
final float v1 = geoGrid.getValue(Math.max(0, col - 1), row);
final float v2 = geoGrid.getValue(col, row);
final float v3 = geoGrid.getValue(Math.min(cols - 1, col + 1), row);
final float v4 = geoGrid.getValue(Math.min(cols - 1, col + 2), row);
return convolveWithVoid(v0, v1, v2, v3, v4);
}
}