/* * 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.Descriptor.Supports; import de.lmu.ifi.dbs.jfeaturelib.Progress; import de.lmu.ifi.dbs.jfeaturelib.features.AbstractFeatureDescriptor; import ij.process.ByteProcessor; import ij.process.ImageProcessor; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import org.apache.log4j.Logger; /** * Computes a normalized distance shape feature as Array List containing the * quadtree values from level 0 to the maximal level. * * This algorithm is described in "Yang Mingqiang, Kpalma Kidiyo and Ronsin * Joseph (2008). A Survey of Shape Feature Extraction Techniques, Pattern * Recognition Techniques, Technology and Applications, Peng-Yeng Yin (Ed.), * ISBN: 978-953-7619-24-4, InTech, Available from: * http://www.intechopen.com/articles/show/title/a_survey_of_shape_feature_extraction_techniques * as "adaptive grid resolution". * * @author Johannes Stadler * @since 09/29/2012 */ public class AdaptiveGridResolution extends AbstractFeatureDescriptor { private static final Logger log = Logger.getLogger(AdaptiveGridResolution.class.getName()); private QuadTreeNode quadtree; private int backgroundColor = 0; private int resizeSize = 64; /* * Computes an ArrayList from the quadtree. */ private List<Double> createFeature() { ArrayList<Double> feature = new ArrayList<>(); while (quadtree.getNextFeatureNode() != null) { if (quadtree.getNextFeatureNode().getValue() == 1) { double size = (double) quadtree.getNextFeatureNode().getSize(); feature.add(quadtree.getNextFeatureNode().getX() + (size / 2.0)); feature.add(quadtree.getNextFeatureNode().getY() + (size / 2.0)); feature.add(size); } quadtree.getNextFeatureNode().setFeature(); } boolean flag = true; while (flag) { flag = false; for (int i = 0; i < feature.size() - 5; i += 3) { if (feature.get(i + 2) < feature.get(i + 5)) { double x = feature.get(i); double y = feature.get(i + 1); double size = feature.get(i + 2); feature.set(i, feature.get(i + 3)); feature.set(i + 1, feature.get(i + 4)); feature.set(i + 2, feature.get(i + 5)); feature.set(i + 3, x); feature.set(i + 4, y); feature.set(i + 5, size); flag = true; } } } return feature; } /** * Constructs a AdaptiveGridResolution object * * @param resizeSize parameter for the image.resize(resizeSize) method, a * bigger number results in a better resolution but a longer calculation * time. */ public AdaptiveGridResolution(int resizeSize) { this.resizeSize = resizeSize; } @Override public void run(ImageProcessor ip) { pcs.firePropertyChange(Progress.getName(), null, Progress.START); ImageProcessor image; { ImagePCA pca2d = new ImagePCA(ip, 0); image = pca2d.getResultImage(); if (!ByteProcessor.class.isAssignableFrom(image.getClass())) { image = (ByteProcessor) image.convertToByte(true); } } double[] centroid; { CentroidFeature cf = new CentroidFeature(); cf.run(image); centroid = cf.getFeatures().get(0); } //turns the image by 180° if the centroid is above the main axis for (int j = 0; j < image.getHeight(); j++) { for (int i = 0; i < image.getHeight(); i++) { if (image.getPixel(j, i) != backgroundColor) { if (i < centroid[1]) { image.rotate(180); break; } } } } if (Double.isNaN(centroid[0]) || Double.isNaN(centroid[1])) { quadtree = new QuadTreeNode(0, 0, 0, resizeSize); return; } //Sets the region of interest to the tangenting rectancle of the shape int x1 = Integer.MAX_VALUE, y1 = Integer.MAX_VALUE, x2 = Integer.MIN_VALUE, y2 = Integer.MIN_VALUE; for (int x = 0; x < image.getWidth(); x++) { for (int y = 0; y < image.getHeight(); y++) { if (image.getPixel(x, y) != backgroundColor) { x1 = Math.min(x1, x); y1 = Math.min(y1, y); x2 = Math.max(x2, x); y2 = Math.max(y2, y); } } } ImageProcessor ipNorm; int resizeSize2; if (y2 - y1 > x2 - x1) { resizeSize2 = (int) (resizeSize * ((double) (x2 - x1) / (double) (y2 - y1))); } else { resizeSize2 = resizeSize; } image.setRoi(x1, y1, (x2 - x1), (y2 - y1)); ipNorm = image.resize(resizeSize2); QuadTreeNode quadtree2 = new QuadTreeNode(-1, 0, 0, resizeSize); //Computes the quadtree while (quadtree2.getNextToDoNode() != null) { QuadTreeNode todo = quadtree2.getNextToDoNode(); int startwert; if (todo.getX() >= ipNorm.getWidth() || todo.getY() >= ipNorm.getHeight()) { startwert = backgroundColor; } else { startwert = ipNorm.getPixel(todo.getX(), todo.getY()); } boolean flag = true; for (int i = 0; i < todo.getSize(); i++) { for (int j = 0; j < todo.getSize(); j++) { if (todo.getX() + i >= ipNorm.getWidth() || todo.getY() >= ipNorm.getHeight()) { if (startwert != backgroundColor) { flag = false; break; } } else { if (startwert != (int) ipNorm.getPixel(todo.getX() + i, todo.getY() + j)) { flag = false; break; } } } if (!flag) { break; } } if (flag) { if (startwert == backgroundColor) { todo.setValue(0); } else { todo.setValue(1); } } else { QuadTreeNode[] children = {new QuadTreeNode(todo, 0), new QuadTreeNode(todo, 1), new QuadTreeNode(todo, 2), new QuadTreeNode(todo, 3)}; todo.setChildren(children); } } quadtree = quadtree2; fillFeatures(); pcs.firePropertyChange(Progress.getName(), null, Progress.END); } @Override public EnumSet<Supports> supports() { return EnumSet.of(Supports.NoChanges, Supports.DOES_16); } @Override public String getDescription() { return "Shape feature descriptor that returns a quadtree as feature"; } private void fillFeatures() { List<Double> feature = this.createFeature(); double[] array = new double[feature.size()]; for (int i = 0; i < feature.size(); i++) { array[i] = feature.get(i); } addData(array); } private static class QuadTreeNode { private QuadTreeNode[] children = null; private int value; private int x; private int y; private int size; private boolean feature = false; private QuadTreeNode(QuadTreeNode parent, int flag) { value = -1; if (flag == 0) { this.size = parent.size / 2; this.x = parent.x; this.y = parent.y; } else if (flag == 1) { this.size = parent.size / 2; this.x = parent.x + size; this.y = parent.y; } else if (flag == 2) { this.size = parent.size / 2; this.x = parent.x + size; this.y = parent.y + size; } else if (flag == 3) { this.size = parent.size / 2; this.x = parent.x; this.y = parent.y + size; } } private QuadTreeNode(int value, int x, int y, int size) { // root = true; this.value = value; this.x = x; this.y = y; this.size = size; feature = false; } private QuadTreeNode getNextToDoNode() { if (children == null && value == -1) { return this; } else if (value == -1) { if (children[0].getNextToDoNode() != null) { return children[0].getNextToDoNode(); } else if (children[1].getNextToDoNode() != null) { return children[1].getNextToDoNode(); } else if (children[2].getNextToDoNode() != null) { return children[2].getNextToDoNode(); } else if (children[3].getNextToDoNode() != null) { return children[3].getNextToDoNode(); } } return null; } private QuadTreeNode getNextFeatureNode() { if (feature == false) { return this; } else if (children != null) { if (children[0].getNextFeatureNode() != null) { return children[0].getNextFeatureNode(); } else if (children[1].getNextFeatureNode() != null) { return children[1].getNextFeatureNode(); } else if (children[2].getNextFeatureNode() != null) { return children[2].getNextFeatureNode(); } else if (children[3].getNextFeatureNode() != null) { return children[3].getNextFeatureNode(); } } return null; } private void setValue(int value) { this.value = value; } private void setChildren(QuadTreeNode[] children) { this.children = children; } private void setFeature() { feature = true; } private int getSize() { return size; } private int getX() { return x; } private int getY() { return y; } private int getValue() { return value; } } }