/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* This program 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.
* This program 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 this program; if not, see http://www.gnu.org/licenses/
*/
package com.bc.ceres.jai;
import com.bc.ceres.core.Assert;
import java.util.Arrays;
/**
* A general filter function.
* This class is used as parameter for the
* {@link com.bc.ceres.jai.operator.GeneralFilterDescriptor GeneralFilter} operation.
*/
public abstract class GeneralFilterFunction {
protected final int width;
protected final int height;
protected final int xOrigin;
protected final int yOrigin;
protected final boolean[] structuringElement;
/**
* Constructs a GeneralFilterFunction.
*
* @param width the width of the kernel.
* @param height the height of the kernel.
* @param xOrigin the X coordinate of the key kernel element.
* @param yOrigin the Y coordinate of the key kernel element.
* @param structuringElement The structuring element with a length equal to {@code width * height}. May be {@code null}.
*/
protected GeneralFilterFunction(int width, int height, int xOrigin, int yOrigin, boolean[] structuringElement) {
Assert.argument(width > 0, "width must be positive");
Assert.argument(height > 0, "height must be positive");
Assert.argument(xOrigin >= 0 && xOrigin < width, "xOrigin out of bounds");
Assert.argument(yOrigin >= 0 && yOrigin < height, "yOrigin out of bounds");
this.width = width;
this.height = height;
this.xOrigin = xOrigin;
this.yOrigin = yOrigin;
if (structuringElement != null) {
Assert.argument(structuringElement.length == width * height, "structuringElement has illegal size");
this.structuringElement = structuringElement.clone();
} else {
this.structuringElement = null;
}
}
/**
* @return the width of the kernel.
*/
public final int getWidth() {
return width;
}
/**
* @return the height of the kernel.
*/
public final int getHeight() {
return height;
}
/**
* @return the X coordinate of the key kernel element.
*/
public final int getXOrigin() {
return xOrigin;
}
/**
* @return the Y coordinate of the key kernel element.
*/
public final int getYOrigin() {
return yOrigin;
}
/**
* @return the number of pixels required to the left of the key element.
*/
public final int getLeftPadding() {
return xOrigin;
}
/**
* @return the number of pixels required to the right of the key element.
*/
public final int getRightPadding() {
return width - xOrigin - 1;
}
/**
* @return the number of pixels required above the key element.
*/
public final int getTopPadding() {
return yOrigin;
}
/**
* @return the number of pixels required below the key element.
*/
public final int getBottomPadding() {
return height - yOrigin - 1;
}
public boolean[] getStructuringElement() {
return structuringElement;
}
public abstract float filter(float[] fdata);
public static final class Min extends GeneralFilterFunction {
public Min(int width, int height, int xOrigin, int yOrigin, boolean[] structuringElement) {
super(width, height, xOrigin, yOrigin, structuringElement);
}
public float filter(float[] fdata) {
final boolean[] se = structuringElement;
float min = Float.POSITIVE_INFINITY;
int n = 0;
for (int i = 0; i < fdata.length; i++) {
float v = fdata[i];
if ((se == null || se[i]) && v < min) {
min = v;
n++;
}
}
return n > 0 ? min : Float.NaN;
}
}
public static final class Max extends GeneralFilterFunction {
public Max(int width, int height, int xOrigin, int yOrigin, boolean[] structuringElement) {
super(width, height, xOrigin, yOrigin, structuringElement);
}
public float filter(float[] fdata) {
final boolean[] se = structuringElement;
float max = Float.NEGATIVE_INFINITY;
int n = 0;
for (int i = 0; i < fdata.length; i++) {
float v = fdata[i];
if ((se == null || se[i]) && v > max) {
max = v;
n++;
}
}
return n > 0 ? max : Float.NaN;
}
}
public static final class Median extends GeneralFilterFunction {
public Median(int width, int height, int xOrigin, int yOrigin, boolean[] structuringElement) {
super(width, height, xOrigin, yOrigin, structuringElement);
}
public float filter(float[] fdata) {
final boolean[] se = structuringElement;
// Note: NaN's are moved to the end of the array
Arrays.sort(fdata);
int n = 0;
for (int i = 0; i < fdata.length; i++) {
float v = fdata[i];
if ((se == null || se[i]) && !Float.isNaN(v)) {
n++;
}
}
if (n == 0) {
return Float.NaN;
} else if (n == 1) {
return fdata[0];
} else if (n % 2 == 1) {
return fdata[n / 2];
} else {
return 0.5F * (fdata[n / 2 - 1] + fdata[n / 2]);
}
}
}
public static final class Mean extends GeneralFilterFunction {
public Mean(int width, int height, int xOrigin, int yOrigin, boolean[] structuringElement) {
super(width, height, xOrigin, yOrigin, structuringElement);
}
public float filter(float[] fdata) {
final boolean[] se = structuringElement;
float sum = 0F;
int n = 0;
for (int i = 0; i < fdata.length; i++) {
float v = fdata[i];
if ((se == null || se[i]) && !Float.isNaN(v)) {
sum += v;
n++;
}
}
return n > 0 ? sum / n : Float.NaN;
}
}
public static final class StdDev extends GeneralFilterFunction {
public StdDev(int width, int height, int xOrigin, int yOrigin, boolean[] structuringElement) {
super(width, height, xOrigin, yOrigin, structuringElement);
}
@Override
public float filter(float[] fdata) {
final boolean[] se = structuringElement;
float sum = 0F;
int n = 0;
for (int i = 0; i < fdata.length; i++) {
float v = fdata[i];
if ((se == null || se[i]) && !Float.isNaN(v)) {
sum += v;
n++;
}
}
if (n > 0) {
float mean = sum / n;
float sqrSum = 0;
for (int i = 0; i < fdata.length; i++) {
float v = fdata[i];
if ((se == null || se[i]) && !Float.isNaN(v)) {
float delta = v - mean;
sqrSum += delta * delta;
}
}
return (float) Math.sqrt(sqrSum / n);
} else {
return Float.NaN;
}
}
}
public static final class Erosion extends GeneralFilterFunction {
public Erosion(int width, int height, int xOrigin, int yOrigin, boolean[] structuringElement) {
super(width, height, xOrigin, yOrigin, structuringElement);
}
public float filter(float[] fdata) {
final boolean[] se = structuringElement;
float min = Float.POSITIVE_INFINITY;
int n = 0;
for (int i = 0; i < fdata.length; i++) {
float v = fdata[i];
if ((se == null || se[i]) && v < min) {
min = v;
n++;
}
}
return n > 0 ? min : Float.NaN;
}
}
public static final class Dilation extends GeneralFilterFunction {
public Dilation(int width, int height, int xOrigin, int yOrigin, boolean[] structuringElement) {
super(width, height, xOrigin, yOrigin, structuringElement);
}
public float filter(float[] fdata) {
final boolean[] se = structuringElement;
float max = Float.NEGATIVE_INFINITY;
int n = 0;
for (int i = 0; i < fdata.length; i++) {
float v = fdata[i];
if ((se == null || se[i]) && v > max) {
max = v;
n++;
}
}
return n > 0 ? max : Float.NaN;
}
}
}