/*
* 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.Progress;
import ij.plugin.filter.PlugInFilter;
import ij.process.ByteProcessor;
import ij.process.ColorProcessor;
import ij.process.ImageProcessor;
import java.util.EnumSet;
/**
* Implementation of the SUSAN (Smallest Univalue Segment Assimilating Nucleus) edge detector.
*
* See also the Wikipedia page: http://en.wikipedia.org/wiki/Corner_detection for more information.</p>
*
* @author Benedikt
*/
public class Susan extends AbstractDescriptor {
private final int IS_EDGE = -1;
private final int NO_EDGE = -16777216;
private int radius;
private int threshold;
private ByteProcessor edgemask;
private int[][] picture;
/**
* Standart constructor with radius 2 and threshold 15
*/
public Susan() {
this.radius = 2;
this.threshold = 15;
}
/**
* @param radius Radius in which the image is looked at
* @param threshold Threshold for difference in luminosity
*/
public Susan(int radius, int threshold) {
this.radius = radius;
this.threshold = threshold;
}
/**
* Defines the capability of the algorithm.
*
* @return descriptor capablities
* @see PlugInFilter
* @see #supports()
*/
@Override
public EnumSet<Supports> supports() {
EnumSet set = EnumSet.of(Supports.DOES_RGB, Supports.DOES_8G, Supports.DOES_8C);
return set;
}
@Override
public void run(ImageProcessor ip) {
startProgress();
this.picture = ip.getIntArray();
if (ip.getClass().isAssignableFrom(ColorProcessor.class)) {
colorToGray();
}
this.edgemask = new ByteProcessor(ip.getWidth(), ip.getHeight());
process();
// write the result back into the image processor
for (int i = 0; i < edgemask.getPixelCount(); i++) {
ip.set(i, edgemask.get(i) != 0 ? IS_EDGE : NO_EDGE);
}
endProgress();
}
/*
* http://users.fmrib.ox.ac.uk/~steve/susan/susan/node6.html Place a
* circular mask around the pixel in question (the nucleus). Using Equation
* 4 calculate the number of pixels within the circular mask which have
* similar brightness to the nucleus. (These pixels define the USAN.) Using
* Equation 3 subtract the USAN size from the geometric threshold to produce
* an edge strength image. Use moment calculations applied to the USAN to
* find the edge direction. Apply non-maximum suppression, thinning and
* sub-pixel estimation, if required.
*/
private void process() {
int WIDTH = edgemask.getWidth();
int HEIGHT = edgemask.getHeight();
int[][] mask = new int[radius * 2 + 1][radius * 2 + 1];
//ignore borders
for (int x = radius; x < WIDTH - radius; x++) {
for (int y = radius; y < HEIGHT - radius; y++) {
for (int maskX = 0; maskX <= radius * 2; maskX++) {
for (int maskY = 0; maskY <= radius * 2; maskY++) {
mask[maskX][maskY] = picture[x - radius + maskX][y - radius + maskY];
}
}
{//horizontal edge
boolean edge = true;
for (int maskX = 0; maskX <= radius * 2; maskX++) {
for (int maskY = 0; edge && maskY <= radius; maskY++) {
edge &= Math.abs(mask[maskX][maskY] - mask[radius][radius]) < threshold;
}
edgemask.set(x, y, edge ? 255 : 0);
}
}
{//vertical edge
boolean edge = true;
for (int maskX = 0; maskX <= radius; maskX++) {
for (int maskY = 0; edge && maskY <= radius * 2; maskY++) {
edge &= Math.abs(mask[maskX][maskY] - mask[radius][radius]) < threshold;
}
edgemask.set(x, y, edge ? 255 : 0);
}
}
}
int progress = (int) 100d * x / WIDTH;
pcs.firePropertyChange(Progress.getName(), null, new Progress(progress));
}
}
/**
* @return the edge mask with edges being marked with 255
*/
public ByteProcessor getEdgemask() {
return edgemask;
}
private void colorToGray() {
// to Gray
for (int i = 0; i < picture.length; i++) {
for (int j = 0; j < picture[0].length; j++) {
picture[i][j] = ARGB_NTSC(picture[i][j]);
}
}
}
/**
* Converts NTSC RGB to gray
*
* See also http://en.wikipedia.org/wiki/Luma_%28video%29
*
* @param p rgb color pixel
* @return gray value
*/
private int ARGB_NTSC(int p) {
int r = (p & 0xff0000) >> 16;
int g = (p & 0xff00) >> 8;
int b = p & 0xff;
return (int) (0.2126 * r + 0.7152 * g + 0.0722 * b);
}
//<editor-fold defaultstate="collapsed" desc="accessors">
/**
* @return Radius in which the image is investigated
*/
public int getRadius() {
return radius;
}
/**
*
* @param radius Radius in which the image is investigated
*/
public void setRadius(int radius) {
this.radius = radius;
}
/**
*
* @return Threshold for difference in luminosity
*/
public int getThreshold() {
return threshold;
}
/**
*
* @param threshold Threshold for difference in luminosity
*/
public void setThreshold(int threshold) {
this.threshold = threshold;
}
//</editor-fold>
}