/*
* 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.Progress;
import ij.plugin.filter.Convolver;
import ij.process.ByteProcessor;
import ij.process.ImageProcessor;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.EnumSet;
/**
* TODO comments
* This class make the edge extraction using DroG operator.
* @author Carmelo Pulvirenti pulvirenti.carmelo@libero.it
* @author rob
*/
public class DroG implements Descriptor {
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
/**
* This is the type of filter that user has choice.
*/
private float edgeFilter[];
private int edge;
private short gKernel;
private float gSigma;
/**
* This is the threshold value. [0-255]
*/
private int threshold;
public DroG() {
edge = 0;
gKernel = 3;
gSigma = 0.5f;
threshold = 20;
if (edge == 0) {
edgeFilter = Service.DroGX(gKernel, gSigma);
} else {
edgeFilter = Service.DroGY(gKernel, gSigma);
}
}
/**
*
* @param edgeXY Choose 0 if you want to detect vertical edges or 1 if you want to detect horizontal edges, default = 0 (X-Edge)
* @param gaussianKernelSize the size of the gaussian kernel, has to be an odd number, default 3
* @param gaussianSigma Gaussian Sigma, has to be greater than 0 and positive, default 0.5
* @param threshold Threshold number from 0-255, default 20
*/
public DroG(int edgeXY, short gaussianKernelSize, float gaussianSigma, int threshold) {
this.edge = edgeXY;
this.gKernel = gaussianKernelSize;
this.gSigma = gaussianSigma;
this.threshold = threshold;
if (!(edge == 0 || edge == 1)) {
throw new IllegalArgumentException("Nonexistent Edge selected!");
}
if (gKernel <= 0) {
throw new IllegalArgumentException("Gaussian Kernel Size is negative or zero!");
}
if (gKernel % 2 == 0) {
throw new IllegalArgumentException("Kernel is not odd!");
}
if (gSigma <= 0) {
throw new IllegalArgumentException("Gaussian Sigma is negative or zero!");
}
if (threshold < 0 || threshold > 255) {
throw new IllegalArgumentException("Threshold is not between 0 and 255!");
}
if (edge == 0) {
edgeFilter = Service.DroGX(gKernel, gSigma);
} else {
edgeFilter = Service.DroGY(gKernel, gSigma);
}
}
@Override
public EnumSet<Supports> supports() {
return DOES_ALL;
}
@Override
public void run(ImageProcessor ip) {
pcs.firePropertyChange(Progress.getName(), null, Progress.START);
ByteProcessor bp = Service.getByteProcessor(ip);
ImageProcessor floatForConv = bp.convertToFloat();
Convolver conv = new Convolver();
conv.setNormalize(false);
conv.convolve(floatForConv, this.edgeFilter, (int) Math.sqrt(this.edgeFilter.length), (int) Math.sqrt(this.edgeFilter.length));
bp = Service.getByteProcessor(floatForConv);
ImageProcessor out = bp.duplicate();
//ImagePlus newImg = new ImagePlus("DroG Result", out);
//newImg.show();
// Lut processor.
int lut[] = new int[256];
int i = 0;
for (; i < threshold; i++) {
lut[i] = 0;
}
for (int j = i; j < lut.length; j++) {
lut[j] = 255;
}
bp.applyTable(lut);
//ImagePlus newImgLut = new ImagePlus("Lut result after DroG", bp);
//newImgLut.show();
ip.insert(bp, 0, 0);
pcs.firePropertyChange(Progress.getName(), null, Progress.END);
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
}
/**
* This is a service class.
*/
class Service {
/*************************************************************************
* public constants
*************************************************************************/
/**
* SobelX kernel.
*/
public final static float[] sobelX = new float[]{
-1, -2, -1,
0, 0, 0,
1, 2, 1
};
/**
* SobelY kernel.
*/
public final static float[] sobelY = new float[]{
-1, 0, 1,
-2, 0, 2,
-1, 0, 1
};
/**
* PrewittX kernel.
*/
public final static float[] prewittX = new float[]{
-1, -1, -1,
0, 0, 0,
1, 1, 1
};
/**
* PrewittY kernel.
*/
public final static float[] prewittY = new float[]{
-1, 0, 1,
-1, 0, 1,
-1, 0, 1
};
/***********************************************************************************
* Methods of Support
* *********************************************************************************/
/**
* This method copies an ImageProcessor in a ByteProcessor.
* @param ip the input ImageProcessor.
* @return ByteProcessor.
*/
public static ByteProcessor getByteProcessor(ImageProcessor ip) {
ByteProcessor bp = new ByteProcessor(ip.getWidth(), ip.getHeight());
for (int y = 0; y < ip.getHeight(); y++) {
for (int x = 0; x < ip.getWidth(); x++) {
bp.set(x, y, ip.getPixel(x, y));
}
}
return bp;
}
/**
* This method makes a LoG kernel in an array[].
* @param window number of row and columns of LoG matrix. It must be an odd.
* @param sigma value of gaussian.
* @return the LoG array.
* @throws IllegalArgumentException if the window is negative zero or it is not odd.
* If sigma is zero or negative.
*/
public static float[] initLaplacianOfGaussian(short window, float sigma) throws IllegalArgumentException {
controlInput(window, sigma);
short aperture = (short) (window / 2);
double[][] LacianOfGaussian = new double[2 * aperture + 1][2 * aperture + 1];
float out[] = new float[(2 * aperture + 1) * (2 * aperture + 1)];
double gauss[][] = new double[2 * aperture + 1][2 * aperture + 1];
int k = 0;
double sum = 0;
for (int dy = -aperture; dy <= aperture; dy++) {
for (int dx = -aperture; dx <= aperture; dx++) {
gauss[dx + aperture][dy + aperture] = Math.exp(-(dx * dx + dy * dy) / (2 * sigma * sigma));
sum += gauss[dx + aperture][dy + aperture];
}
}
// Normalizzation of gaussian
for (int dy = -aperture; dy <= aperture; dy++) {
for (int dx = -aperture; dx <= aperture; dx++) {
gauss[dx + aperture][dy + aperture] = gauss[dx + aperture][dy + aperture] / sum;
}
}
sum = 0;
//Laplacian
for (int dy = -aperture; dy <= aperture; dy++) {
for (int dx = -aperture; dx <= aperture; dx++) {
LacianOfGaussian[dx + aperture][dy + aperture] = gauss[dx + aperture][dy + aperture] * (dx * dx + dy * dy - 2 * Math.pow(sigma, 2)) / Math.pow(sigma, 4);
sum += LacianOfGaussian[dx + aperture][dy + aperture];
}
}
sum = sum / (window * window);
for (int dy = -aperture; dy <= aperture; dy++) {
for (int dx = -aperture; dx <= aperture; dx++) {
out[k++] = (float) (LacianOfGaussian[dx + aperture][dy + aperture] - sum);
}
}
return out;
}
/**
* This method makes a symmetrical gaussian in an array[].
* @param window number of row and columns of gaussian matrix. It must be an odd.
* @param sigma value of gaussian.
* @return the gaussian array.
* @throws IllegalArgumentException if the window is negative zero or it is not odd.
* If sigma is zero or negative.
*/
public static float[] initGaussianKernel(short window, float sigma) throws IllegalArgumentException {
controlInput(window, sigma);
short aperture = (short) (window / 2);
float[][] gaussianKernel = new float[2 * aperture + 1][2 * aperture + 1];
float out[] = new float[(2 * aperture + 1) * (2 * aperture + 1)];
int k = 0;
float sum = 0;
for (int dy = -aperture; dy <= aperture; dy++) {
for (int dx = -aperture; dx <= aperture; dx++) {
gaussianKernel[dx + aperture][dy + aperture] = (float) Math.exp(-(dx * dx + dy * dy) / (2 * sigma * sigma));
sum += gaussianKernel[dx + aperture][dy + aperture];
}
}
for (int dy = -aperture; dy <= aperture; dy++) {
for (int dx = -aperture; dx <= aperture; dx++) {
out[k++] = gaussianKernel[dx + aperture][dy + aperture] / sum;
}
}
return out;
}
/**
* This function makes a discrete DroGX kernel in a float array.
* DroGX(x,y,sigma) = (-y/sigma^2)* EXP(-(x^2+y^2)/(2*sigma^2))
* @param window number of row and columns of gaussian matrix. It must be an odd.
* @param sigma value of gaussian.
* @return the array with DroGX kernel.
* @throws IllegalArgumentException if the window is negative zero or it is not odd.
* If sigma is zero or negative.
*/
public static float[] DroGX(short window, float sigma) throws IllegalArgumentException {
controlInput(window, sigma);
short aperture = (short) (window / 2);
float[][] Drog = new float[2 * aperture + 1][2 * aperture + 1];
float out[] = new float[(2 * aperture + 1) * (2 * aperture + 1)];
int k = 0;
float sum = 0;
for (float dy = -aperture; dy <= aperture; dy = dy + 1) {
for (float dx = -aperture; dx <= aperture; dx = dx + 1) {
float r = ((dx * dx) + (dy * dy));
float one = (float) -(dy / Math.pow(sigma, 2));
float two = (float) (-r / (2.0 * Math.pow(sigma, 2)));
float three = (float) Math.exp(two);
Drog[(int) (dx + aperture)][(int) (dy + aperture)] = (float) (one * three);
sum += Drog[(int) (dx + aperture)][(int) (dy + aperture)];
}
}
sum = sum / (window * window);
for (float dy = -aperture; dy <= aperture; dy = dy + 1) {
for (float dx = -aperture; dx <= aperture; dx = dx + 1) {
out[k++] = Drog[(int) (dx + aperture)][(int) (dy + aperture)] - sum;
}
}
return out;
}
/**
* This function makes a discrete DroGY kernel in a float array.
* DroGY(x,y,sigma) = (-x/sigma^2)* EXP(-(x^2+y^2)/(2*sigma^2))
* @param window number of row and columns of the gaussian matrix. It must be an odd.
* @param sigma value of gaussian.
* @return the array with DroGY kernel.
* @throws IllegalArgumentException if the window is negative zero or it is not odd.
* If sigma is zero or negative.
*/
public static float[] DroGY(short window, float sigma) throws IllegalArgumentException {
controlInput(window, sigma);
short aperture = (short) (window / 2);
float[][] Drog = new float[2 * aperture + 1][2 * aperture + 1];
float out[] = new float[(2 * aperture + 1) * (2 * aperture + 1)];
int k = 0;
float sum = 0;
for (float dy = -aperture; dy <= aperture; dy = dy + 1) {
for (float dx = -aperture; dx <= aperture; dx = dx + 1) {
float r = ((dx * dx) + (dy * dy));
float one = (float) -(dx / Math.pow(sigma, 2));
float two = (float) (-r / (2.0 * Math.pow(sigma, 2)));
float three = (float) Math.exp(two);
Drog[(int) (dx + aperture)][(int) (dy + aperture)] = (float) (one * three);
sum += Drog[(int) (dx + aperture)][(int) (dy + aperture)];
}
}
sum = sum / (window * window);
for (float dy = -aperture; dy <= aperture; dy = dy + 1) {
for (float dx = -aperture; dx <= aperture; dx = dx + 1) {
out[k++] = Drog[(int) (dx + aperture)][(int) (dy + aperture)] - sum;
}
}
return out;
}
/**
* This method checks the gaussian value.
* @param window the window of the gaussian.
* @param sigma the gaussian sigma.
* @throws IllegalArgumentException if the window is negative zero or it is not odd.
* If sigma is zero or negative.
*/
private static void controlInput(short window, float sigma) throws IllegalArgumentException {
if (window % 2 == 0) {
throw new IllegalArgumentException("Window isn't an odd.");
}
if (window <= 0) {
throw new IllegalArgumentException("Window is negative or zero");
}
if (sigma <= 0) {
throw new IllegalArgumentException("Sigma of the gaussian is zero or negative.");
}
}
}