/*******************************************************************************
* Copyright 2012 Geoscience Australia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package au.gov.ga.earthsci.worldwind.common.layers.delegate.transformer;
import gov.nasa.worldwind.avlist.AVList;
import java.awt.image.BufferedImage;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.w3c.dom.Element;
import au.gov.ga.earthsci.worldwind.common.layers.delegate.IDelegate;
import au.gov.ga.earthsci.worldwind.common.layers.delegate.IDelegatorTile;
import au.gov.ga.earthsci.worldwind.common.layers.delegate.IImageTransformerDelegate;
/**
* Applies a filter to the retrieved image tiles to remove striping noise using a
* combination of lowpass and highpass filtering.
* <p/>
* Based on the technique described at http://isis.astrogeology.usgs.gov/IsisWorkshop/index.php/Removing_Striping_Noise_from_Image_Data
* <p/>
* As described in the article, a good rule of thumb for the size of filter to apply is:
* <ol>
* <li>Make the size of the lowpass filter large enough to encompass the striping (e.g. twice the size of the striping)
* <li>Make the size of the highpass filter small enough to fit within the striping (e.g. half the size of the striping)
* </ol>
* <p/>
* <code><Delegate>StripingFilterTransformer(lowPassCols,lowPassRows,highPassCols,highPassRows)</Delegate></code>
* <ul>
* <li>lowPassCols = the filter size for the low pass filter applied to the columns of the image (integer number of pixels)
* <li>lowPassRows = the filter size for the low pass filter applied to the rows of the image (integer number of pixels)
* <li>highPassCols = the filter size for the high pass filter applied to the columns of the image (integer number of pixels)
* <li>highPassRows = the filter size for the high pass filter applied to the rows of the image (integer number of pixels)
* </ul>
* <b>Note:</b> If the lowPass and highPass filters are the same size for columns or rows, no effect will be seen in that direction of the image
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
public class StripingFilterTransformerDelegate implements IImageTransformerDelegate
{
private final static String DEFINITION_STRING = "StripingFilterTransformer";
protected final int lowPassCols;
protected final int lowPassRows;
protected final int highPassCols;
protected final int highPassRows;
@SuppressWarnings("unused")
private StripingFilterTransformerDelegate()
{
this(1, 1, 1, 1);
}
public StripingFilterTransformerDelegate(int lowPassCols, int lowPassRows, int highPassCols,
int highPassRows)
{
this.lowPassCols = lowPassCols;
this.lowPassRows = lowPassRows;
this.highPassCols = highPassCols;
this.highPassRows = highPassRows;
}
@Override
public BufferedImage transformImage(BufferedImage image, IDelegatorTile tile)
{
return filter(image, lowPassCols, lowPassRows, highPassCols, highPassRows);
}
@Override
public IDelegate fromDefinition(String definition, Element layerElement, AVList params)
{
if (definition.toLowerCase().startsWith(DEFINITION_STRING.toLowerCase()))
{
Pattern pattern = Pattern.compile("(?:\\((\\d+),(\\d+),(\\d+),(\\d+)\\))");
Matcher matcher = pattern.matcher(definition);
if (matcher.find())
{
int lowPassCols = Integer.parseInt(matcher.group(1));
int lowPassRows = Integer.parseInt(matcher.group(2));
int highPassCols = Integer.parseInt(matcher.group(3));
int highPassRows = Integer.parseInt(matcher.group(4));
return new StripingFilterTransformerDelegate(lowPassCols, lowPassRows,
highPassCols, highPassRows);
}
}
return null;
}
@Override
public String toDefinition(Element layerElement)
{
return DEFINITION_STRING + "(" + lowPassCols + "," + lowPassRows + "," + highPassCols + ","
+ highPassRows + ")";
}
protected static BufferedImage filter(BufferedImage image, int lowPassCols, int lowPassRows,
int highPassCols, int highPassRows)
{
int width = image.getWidth();
int height = image.getHeight();
float[][][] array = imageToArray(image);
float[][][] lowpass = average(array, lowPassCols, lowPassRows);
float[][][] highpass = average(array, highPassCols, highPassRows);
subtract(array, highpass, highpass);
add(lowpass, highpass, array);
return arrayToImage(array, width, height);
}
protected static float[][][] imageToArray(BufferedImage image)
{
int width = image.getWidth();
int height = image.getHeight();
int bands = 4;
float[][][] array = new float[width][height][bands];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int argb = image.getRGB(x, y);
int a = (argb >> 24) & 0xff;
int r = (argb >> 16) & 0xff;
int g = (argb >> 8) & 0xff;
int b = (argb) & 0xff;
array[x][y][0] = (a / 255f) * 2f - 1f;
array[x][y][1] = (r / 255f) * 2f - 1f;
array[x][y][2] = (g / 255f) * 2f - 1f;
array[x][y][3] = (b / 255f) * 2f - 1f;
}
}
return array;
}
protected static BufferedImage arrayToImage(float[][][] array, int width, int height)
{
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int a = (int) ((array[x][y][0] + 1f) / 2f * 255f);
int r = (int) ((array[x][y][1] + 1f) / 2f * 255f);
int g = (int) ((array[x][y][2] + 1f) / 2f * 255f);
int b = (int) ((array[x][y][3] + 1f) / 2f * 255f);
int argb = (a & 0xff) << 24 | (r & 0xff) << 16 | (g & 0xff) << 8 | (b & 0xff);
image.setRGB(x, y, argb);
}
}
return image;
}
protected static float[][][] average(float[][][] image, int windowWidth, int windowHeight)
{
int width = image.length;
int height = image[0].length;
int bands = image[0][0].length;
float[][][] array = new float[width][height][bands];
float[][] windowHorizontalSum = new float[width][height];
float[] windowVerticalEdgeSum = new float[height];
//skip alpha
for (int b = 1; b < bands; b++)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
if (y == 0)
{
float sum = 0;
for (int wy = 0; wy < windowHeight; wy++)
{
int sy = clamp(y + wy - windowHeight / 2, 0, height - 1);
sum += image[x][sy][b];
}
windowHorizontalSum[x][y] = sum;
}
else
{
int ssy = clamp(y - windowHeight / 2 - 1, 0, height - 1);
int say = clamp(y + windowHeight / 2, 0, height - 1);
windowHorizontalSum[x][y] =
windowHorizontalSum[x][y - 1] - image[x][ssy][b] + image[x][say][b];
}
}
}
for (int y = 0; y < height; y++)
{
float sum = 0;
for (int wx = 0; wx < windowWidth; wx++)
{
int sx = clamp(wx - windowWidth / 2, 0, width - 1);
sum += image[sx][y][b];
}
windowVerticalEdgeSum[y] = sum;
}
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
if (x == 0)
{
if (y == 0)
{
float sum = 0;
for (int wx = 0; wx < windowWidth; wx++)
{
int sx = clamp(wx - windowWidth / 2, 0, width - 1);
sum += windowHorizontalSum[sx][y];
}
array[x][y][b] = sum;
}
else
{
int ssy = clamp(y - windowHeight / 2 - 1, 0, height - 1);
int say = clamp(y + windowHeight / 2, 0, height - 1);
array[x][y][b] =
array[x][y - 1][b] - windowVerticalEdgeSum[ssy]
+ windowVerticalEdgeSum[say];
}
}
else
{
int ssx = clamp(x - windowWidth / 2 - 1, 0, width - 1);
int sax = clamp(x + windowWidth / 2, 0, width - 1);
array[x][y][b] =
array[x - 1][y][b] - windowHorizontalSum[ssx][y]
+ windowHorizontalSum[sax][y];
}
}
}
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
array[x][y][b] /= windowWidth * windowHeight;
}
}
}
return array;
}
protected static int clamp(int value, int min, int max)
{
return value > max ? max : value < min ? min : value;
}
protected static float clamp(float value, float min, float max)
{
return value > max ? max : value < min ? min : value;
}
protected static void subtract(float[][][] image1, float[][][] image2, float[][][] store)
{
int width = image1.length;
int height = image1[0].length;
int bands = image1[0][0].length;
//skip alpha
for (int b = 1; b < bands; b++)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
store[x][y][b] = clamp(image1[x][y][b] - image2[x][y][b], -1, 1);
}
}
}
}
protected static void add(float[][][] image1, float[][][] image2, float[][][] store)
{
int width = image1.length;
int height = image1[0].length;
int bands = image1[0][0].length;
//skip alpha
for (int b = 1; b < bands; b++)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
store[x][y][b] = clamp(image1[x][y][b] + image2[x][y][b], -1, 1);
}
}
}
}
}