/*
* 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.shapeFeatures;
import de.lmu.ifi.dbs.jfeaturelib.LibProperties;
import de.lmu.ifi.dbs.jfeaturelib.features.AbstractFeatureDescriptor;
import ij.process.ByteProcessor;
import ij.process.ImageProcessor;
import java.awt.Polygon;
import java.io.IOException;
import java.util.EnumSet;
import org.apache.log4j.Logger;
/**
* The Descriptor represents a binarized shape model by the SquareModelShapreMatrix, also known as GridDescriptor.
*
* A brief explanation can be found in
* http://cdn.intechopen.com/pdfs/5781/InTech-A_survey_of_shape_feature_extraction_techniques.pdf
*
* The original paper is written by<br> J. Flusser, “Invariant shape description and measure of object similarity,” in
* Proc. 4th, International Conference on Image Processing and its Applications, 1992, pp. 139-142.
*
* @author Johannes Stadler
* @since v1.1.0
*/
public class SquareModelShapeMatrix extends AbstractFeatureDescriptor {
private final static Logger log = Logger.getLogger(SquareModelShapeMatrix.class);
private int[][] feature;
private int backgroundColor = 0;
int matrixDimension = 30;
/**
* Initializes the SquareModelShapeMatrix with default background color (0/black) and a default matrix size.
*/
public SquareModelShapeMatrix() {
}
public SquareModelShapeMatrix(int matrixDimension) {
setMatrixDimension(matrixDimension);
}
public SquareModelShapeMatrix(int matrixDimension, int backgroundColor) {
setMatrixDimension(matrixDimension);
this.backgroundColor = backgroundColor;
}
@Override
public void setProperties(LibProperties properties) throws IOException {
setMatrixDimension(properties.getInteger(LibProperties.SMSM_DIMENSIONS, 30));
backgroundColor = properties.getInteger(LibProperties.SMSM_BG_COLOR, 0);
}
/**
* @return the actual binarized SquareModelShapeMatrix
*/
public int[][] getMatrix() {
return feature;
}
/**
* calculates the euclidean distance
*
* @param x1
* @param y1
* @param x2
* @param y2
* @return euclidean distance
*/
private double distance(int x1, int y1, int x2, int y2) {
double dx = (double) x1 - (double) x2;
double dy = (double) y1 - (double) y2;
return Math.sqrt(dx * dx + dy * dy);
}
@Override
public void run(ImageProcessor ip) {
startProgress();
if (!ByteProcessor.class.isAssignableFrom(ip.getClass())) {
ip = ip.convertToByte(true);
}
int xC, yC, xMax = 0, yMax = 0;
double bigSquareSize = Double.MIN_VALUE; //angle
{
CentroidFeature cf = new CentroidFeature();
cf.run(ip);
double[] xCyC = cf.getFeatures().get(0);
xC = (int) xCyC[0];
yC = (int) xCyC[1];
}
for (int i = 0; i < ip.getWidth(); i++) {
double dist;
for (int j = 0; j < ip.getHeight(); j++) {
dist = distance(xC, yC, i, j);
if (ip.get(i, j) != backgroundColor && dist > bigSquareSize) {
bigSquareSize = dist;
xMax = i;
yMax = j;
}
}
}
int deltax = Math.abs(xC - xMax);
int deltay = Math.abs(yC - yMax);
int xA = xC - deltax;
int yA = yC - deltay;
int xB = xC + deltax;
int yB = yC + deltay;
int xP1 = xA - deltay;
int yP1 = yA + deltax;
int xP2 = xA + deltay;
int yP2 = yA - deltax;
// int xP3 = xB + deltay;
// int yP3 = yB - deltax;
int xP4 = xB - deltay;
int yP4 = yB + deltax;
double xVektorP12 = xP2 - xP1;
double yVektorP12 = yP2 - yP1;
double xVektorP14 = xP4 - xP1;
double yVektorP14 = yP4 - yP1;
log.debug("Delta: " + deltax + " " + deltay);
feature = new int[matrixDimension][matrixDimension];
for (int i = 0; i < matrixDimension; i++) {
for (int j = 0; j < matrixDimension; j++) {
int x1 = (int) Math.round(xP1 + (xVektorP12 * ((double) i / matrixDimension)) + (xVektorP14 * ((double) j / matrixDimension)));
int y1 = (int) Math.round(yP1 + (yVektorP12 * ((double) i / matrixDimension)) + (yVektorP14 * ((double) j / matrixDimension)));
int x2 = (int) Math.round(xP1 + (xVektorP12 * (((double) i + 1) / matrixDimension)) + (xVektorP14 * ((double) j / matrixDimension)));
int y2 = (int) Math.round(yP1 + (yVektorP12 * (((double) i + 1) / matrixDimension)) + (yVektorP14 * ((double) j / matrixDimension)));
int x3 = (int) Math.round(xP1 + (xVektorP12 * ((double) i / matrixDimension)) + (xVektorP14 * (((double) j + 1) / matrixDimension)));
int y3 = (int) Math.round(yP1 + (yVektorP12 * ((double) i / matrixDimension)) + (yVektorP14 * (((double) j + 1) / matrixDimension)));
int x4 = (int) Math.round(xP1 + (xVektorP12 * (((double) i + 1) / matrixDimension)) + (xVektorP14 * (((double) j + 1) / matrixDimension)));
int y4 = (int) Math.round(yP1 + (yVektorP12 * (((double) i + 1) / matrixDimension)) + (yVektorP14 * (((double) j + 1) / matrixDimension)));
int MinX = Math.min(x1, Math.min(x2, Math.min(x3, x4)));
int MaxX = Math.max(x1, Math.max(x2, Math.max(x3, x4)));
int MinY = Math.min(y1, Math.min(y2, Math.min(y3, y4)));
int MaxY = Math.max(y1, Math.max(y2, Math.max(y3, y4)));
int x[] = {x1, x2, x3, x4};
int y[] = {y1, y2, y3, y4};
Polygon poly = new Polygon(x, y, 4);
int counter = 0;
for (int k = MinX; k <= MaxX; k++) {
for (int l = MinY; l <= MaxY; l++) {
if (poly.contains(k, l)) {
if (k < ip.getWidth() && l < ip.getHeight() && ip.getPixel(k, l) != backgroundColor) {
counter++;
} else {
counter--;
}
}
}
}
if (counter <= 0) {
feature[i][j] = 0;
} else {
feature[i][j] = 1;
}
}
}
createFeature();
endProgress();
}
/**
* concatenates the matrix into a single long array
*/
private void createFeature() {
double[] x = new double[feature.length * feature.length];
for (int i = 0; i < feature.length; i++) {
for (int j = 0; j < feature.length; j++) {
x[(i * feature.length) + j] = feature[i][j];
}
}
addData(x);
}
@Override
public EnumSet<Supports> supports() {
return EnumSet.of(Supports.NoChanges, Supports.DOES_32, Supports.DOES_16,
Supports.DOES_8C, Supports.DOES_8G, Supports.DOES_RGB);
}
@Override
public String getDescription() {
return "Shape feature descriptor that returns the shape matrix as feature";
}
public void setBackgroundColor(int backgroundColor) {
this.backgroundColor = backgroundColor;
}
public void setMatrixDimension(int matrixDimension) {
if (matrixDimension <= 0) {
throw new IllegalArgumentException("dimension must be > 0 but was " + matrixDimension);
}
this.matrixDimension = matrixDimension;
}
}