/*
* This file is part of the JFeatureLib project: https://github.com/locked-fg/JFeatureLib
* JFeatureLib 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.
*
* JFeatureLib 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 JFeatureLib; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* You are kindly asked to refer to the papers of the according authors which
* should be mentioned in the Javadocs of the respective classes as well as the
* JFeatureLib project itself.
*
* Hints how to cite the projects can be found at
* https://github.com/locked-fg/JFeatureLib/wiki/Citation
*/
package de.lmu.ifi.dbs.jfeaturelib.edgeDetector;
import de.lmu.ifi.dbs.jfeaturelib.Descriptor;
import de.lmu.ifi.dbs.jfeaturelib.Descriptor.Supports;
import de.lmu.ifi.dbs.jfeaturelib.Progress;
import de.lmu.ifi.dbs.utilities.Arrays2;
import ij.plugin.filter.PlugInFilter;
import ij.process.ByteProcessor;
import ij.process.ImageProcessor;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.EnumSet;
/**
* Performs convolution with a given Kernel directly on this image.
*
* Basiacally it is just a wrapper for ImageJ's ImageProcessor.convolve().
*
* Predefined masks are SOBEL, SCHARR, PREWITT
*
* @see ImageProcessor#convolve(float[], int, int)
* @author Benedikt
* @author Franz
*/
public class Kernel extends AbstractDescriptor {
/**
* Standard 3x3 SOBEL mask (1 0 -1, 2, 0, -2, 1, 0, -1 )
*
* See http://en.wikipedia.org/wiki/Sobel_operator
*/
public static final float[] SOBEL = new float[]{
1, 0, -1,
2, 0, -2,
1, 0, -1
};
/**
* Standard 3x3 SCHARR mask (1 0 -1, 2, 0, -2, 1, 0, -1 )
*
* See http://en.wikipedia.org/wiki/Sobel_operator
*/
public static final float[] SCHARR = new float[]{
3, 0, -3,
10, 0, -10,
3, 0, -3
};
/**
* 3x3 PREWITT mask (-1, 0, 1, ...)
*
* See http://en.wikipedia.org/wiki/Prewitt_operator
*/
public static final float[] PREWITT = new float[]{
-1, 0, 1,
-1, 0, 1,
-1, 0, 1
};
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private ByteProcessor image;
private int treshold = 0;
float[] kernel;
/**
* Constructs a new detector with standart Sobel kernel
*/
public Kernel() {
this(SOBEL);
}
/**
* Constructs a new detector with given double[] kernel.
*
* The double array is converted immediately to a float array.
*
* @param kernel double[] with kernel
*/
public Kernel(double[] kernel) {
this(Arrays2.convertToFloat(kernel));
}
/**
* Constructs a new detector with given float[] kernel and according width.
*
* @param kernel float[] with kernel
*/
public Kernel(float[] kernel) {
setKernel(kernel);
}
/**
* Constructs a new detector with given float[] kernel and according width.
*
* @param kernel float[] with kernel
* @param width width of kernel
*/
public Kernel(float[] kernel, int width) {
this.kernel = kernel;
}
//<editor-fold defaultstate="collapsed" desc="accessors">
/**
* Return the convolution kernel before transformation
*
* @return float[] with kernel
*/
public float[] getKernel() {
return kernel;
}
/**
* Sets the convolution kernel (must be odd sized).
*
* @param kernel float[] with kernel
* @throws IllegalArgumentException if the size is even
*/
public void setKernel(float[] kernel) {
if (kernel.length % 2 == 0) {
throw new IllegalArgumentException("kernel size must be odd but was " + kernel.length);
}
this.kernel = kernel;
}
/**
* Returns width of the kernel
*
* @return int with width of kernel
*/
public int getKernelWidth() {
return Math.round((float) Math.sqrt(kernel.length + 1.0f));
}
/**
* Setter for width of the kernel
*
* @param kernelWidth int with width
* @deprecated since 1.5.0
*/
@Deprecated
public void setKernelWidth(int kernelWidth) {
throw new UnsupportedOperationException("width cannot be set");
}
/**
* Returns treshold
*
* @return treshold
*/
public int getTreshold() {
return treshold;
}
/**
* Setter for treshold, default zero
*
* @param treshold
*/
public void setTreshold(int treshold) {
if (treshold < 255) {
this.treshold = treshold;
} else {
this.treshold = 254;
}
}
/**
* Proceses the image applying the kernel in x- and y-direction
*/
public void process() {
startProgress();
final int width = image.getWidth();
final int height = image.getHeight();
final int kernelWidth = Math.round((float) Math.sqrt(kernel.length + 1.0f));
ByteProcessor imgX = new ByteProcessor(image.createImage());
ByteProcessor imgY = new ByteProcessor(image.createImage());
float[] kernelX = kernel;
float[] kernelY = new float[kernelWidth * kernelWidth];
float[][] kernelX2D = new float[kernelWidth][kernelWidth];
int i = 0;
for (int x = 0; x < kernelWidth; x++) {
for (int y = 0; y < kernelWidth; y++) {
kernelX2D[x][y] = kernelX[i++];
}
}
float[][] kernelY2D = rotateFloatCW(kernelX2D);
i = 0;
for (int x = 0; x < kernelWidth; x++) {
for (int y = 0; y < kernelWidth; y++) {
kernelY[i++] = kernelY2D[x][y];
}
int progress = (int) Math.round(i * (100.0 / (double) (kernelWidth * kernelWidth)));
pcs.firePropertyChange(Progress.getName(), null, new Progress(progress, "Step " + i + " of " + kernelWidth * kernelWidth));
}
imgX.convolve(kernelX, kernelWidth, kernelWidth);
imgY.convolve(kernelY, kernelWidth, kernelWidth);
int[][] imgXa = imgX.getIntArray();
int[][] imgYa = imgY.getIntArray();
int[][] imgA = new int[width][height];
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
imgA[x][y] = (int) Math.round(Math.sqrt(imgXa[x][y] * imgXa[x][y] + imgYa[x][y] * imgYa[x][y]));
if (imgA[x][y] > 255) {
imgA[x][y] = 255;
} else if (imgA[x][y] < treshold) {
imgA[x][y] = 0;
}
}
}
image.setIntArray(imgA);
endProgress();
}
private float[][] rotateFloatCW(float[][] mat) {
final int M = mat.length;
final int N = mat[0].length;
float[][] ret = new float[N][M];
for (int r = 0; r < M; r++) {
for (int c = 0; c < N; c++) {
ret[c][M - 1 - r] = mat[r][c];
}
}
return ret;
}
/**
* Defines the capability of the algorithm.
*
* @see PlugInFilter
* @see #supports()
*/
@Override
public EnumSet<Supports> supports() {
EnumSet set = EnumSet.of(
Supports.NoChanges,
Supports.DOES_8C,
Supports.DOES_8G,
Supports.DOES_RGB,
Supports.DOES_16);
return set;
}
/**
* @param ip ImageProcessor of the source image
*/
@Override
public void run(ImageProcessor ip) {
if (!ByteProcessor.class.isAssignableFrom(ip.getClass())) {
ip = ip.convertToByte(true);
}
this.image = (ByteProcessor) ip;
pcs.firePropertyChange(Progress.getName(), null, Progress.START);
process();
pcs.firePropertyChange(Progress.getName(), null, Progress.END);
}
}