content: /*
   * Copyright (C) 2017 Andreas Maier
   * CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
   */
  package edu.stanford.rsl.conrad.segmentation;
  
  import java.util.ArrayList;
  
  import org.apache.commons.math3.util.FastMath;
  
  import edu.stanford.rsl.conrad.data.numeric.MultiChannelGrid2D;
  import edu.stanford.rsl.jpop.utils.UserUtil;
  import weka.core.Attribute;
  import weka.core.Instances;
  
  public class HistogramFeatureExtractor extends GridFeatureExtractor {
  
  	/**
  	 * 
  	 */
  	private static final long serialVersionUID = -8201678340342264383L;
  	int numberOfBins = 64;
  	int patchSize = 2;	//25 Pixel neighborhood
  	// Atmung 0-14484,	Rotation 0-502199
  	float min = 0;
  	float max = 14484;
  	int HistoFeatures = 6;
  
  	public Instances getInstances() {
  		return instances;
  	}
  
  	protected Attribute generateClassAttribute() {
  		Attribute classAttribute = null;
  		if (classValues == null) {
  			classAttribute = new Attribute("Class");
  		} else {
  			classAttribute = new Attribute(className, classValues);
  		}
  		classAttribute = new Attribute(className);
  		return classAttribute;
  	}
  
  	// compute histogram based features: mean grey value, standard deviation,
  	// entropy; coefficient of variance, skewness and kurtosis
  	public double[] extractHistogramFeatures(double[] hist) {
  
  		double histSum = 0.0;
  		double AveGreyVal, Variance, StandardDev, Entropy = 0.0;
  		double tmp;
  		double accum = 0.0;
  		double accum2 = 0.0;
  		double accum3 = 0.0;
  		double accum4 = 0.0;
  		double len = hist.length;
  		double[] feature = new double[7];
  
  		// calculate mean grey value of histogram
  		for (int i = 0; i < len; i++) {
  			histSum += hist[i];
  		}
  		AveGreyVal = histSum / len;
  		// write mean grey value in feature array
  		feature[0] = AveGreyVal;
  
  		// Normalize histogram, that is makes the sum of all bins equal to 1.
  		double[] normalizedHist = new double[hist.length];
  		for (int i = 0; i < hist.length; i++) {
  			normalizedHist[i] = hist[i] / histSum;
  		}
  
  		// calculate variance of histogram
  		for (int i = 0; i < len; i++) {
  			tmp = normalizedHist[i] - AveGreyVal;
  			accum += tmp * tmp;
  			accum2 += tmp;
  		}
  		Variance = (accum - (accum2 * accum2 / len)) / (len - 1);
  		StandardDev = Math.sqrt(Variance);
  		// write standard deviation in feature array
  		feature[1] = StandardDev;
  
  		// calculate entropy of histogram
  		for (int i = 0; i < len; i++) {
  			if (normalizedHist[i] > 0) {
  				tmp = normalizedHist[i] / histSum;
  				Entropy -= tmp * (Math.log(tmp) / Math.log(2));
  			}
  		}
  		feature[2] = Entropy;
  
  		// calculate coefficient of variance of histogram and write it in
  		// feature array
  		feature[3] = (StandardDev / AveGreyVal) * 100.0;
  
  		// calculate skewness of histogram
  		for (int i = 0; i < len; i++) {
  			accum3 += FastMath.pow((normalizedHist[i] - AveGreyVal), 3.0);
  
  		}
  		accum3 /= Variance * Math.sqrt(Variance);
  		// write skewness in feature array
  		feature[4] = (len / ((len - 1.0) * (len - 2))) * accum3;
  
  		// calculate kurtosis of histogram
  		for (int i = 0; i < len; i++) {
  			accum4 += FastMath.pow((normalizedHist[i] - AveGreyVal), 4.0);
  		}
  		accum4 /= FastMath.pow(StandardDev, 4.0);
  
  		double coeffs = (len * (len + 1)) / ((len - 1) * (len - 2) * (len - 3));
  		double termTwo = (3 * FastMath.pow((len - 1), 2.0))
  				/ ((len - 2) * (len - 3));
  		// write kurtosis in feature array
  		feature[5] = (coeffs * accum4) - termTwo;
  
  		return feature;
  	}
  
  	@Override
  	public void configure() throws Exception {
  
  
  		patchSize = UserUtil
  				.queryInt("Set histogram patch size", patchSize);
  		numberOfBins = UserUtil.queryInt("Select number of histogram bins",
  				numberOfBins);
  		min = (float) UserUtil.queryDouble("Minimal histogram value", min);
  		max = (float) UserUtil.queryDouble("Maximal histogram value ", max);
  		configured = true;
  
  		String[] nameString = { "mean grey value", "standard deviation",
  				"entropy", "coefficient of variance", "skewness", "kurtosis" };
  
  		if (this.dataGrid instanceof MultiChannelGrid2D) {
  			MultiChannelGrid2D multiChannelGrid = (MultiChannelGrid2D) this.dataGrid;
  
  			attribs = new ArrayList<Attribute>(HistoFeatures
  					* multiChannelGrid.getNumberOfChannels() + 1);
  
  			for (int i = 0; i < multiChannelGrid.getNumberOfChannels(); i++) {
  				for (int j = 0; j < HistoFeatures; j++) {
  					String name = "Slice " + i + ": Histogram " + nameString[j];
  					attribs.add(new weka.core.Attribute(name));
  				}
  			}
  
  		} else {
  
  			attribs = new ArrayList<Attribute>(HistoFeatures + 1);
  			for (int j = 0; j < HistoFeatures; j++) {
  				String name = ": Histogram " + nameString[j];
  				attribs.add(new weka.core.Attribute(name));
  			}
  
  		}
  		Attribute classAttribute = generateClassAttribute();
  		attribs.add(classAttribute);
  		// leeres set von feature vectoren
  		instances = new Instances(className, attribs, 0);
  		instances.setClass(classAttribute);
  
  	}
  
  	@Override
  	public double[] extractFeatureAtIndex(int x, int y) {
  
  		double[] result;
  
  		if (this.dataGrid instanceof MultiChannelGrid2D) {
  			MultiChannelGrid2D multiChannelGrid = (MultiChannelGrid2D) this.dataGrid;
  			result = new double[(HistoFeatures
  					* multiChannelGrid.getNumberOfChannels() + 1)];
  
  			for (int c = 0; c < multiChannelGrid.getNumberOfChannels(); c++) {
  
  				double[] histo = new double[numberOfBins + 1];
  
  				for (int j = y - patchSize; j <= y + patchSize; j++) {
  					if (j < 0)
  						continue;
  					if (j >= this.dataGrid.getSize()[1])
  						continue;
  					for (int i = x - patchSize; i <= x + patchSize; i++) {
  						if (i < 0)
  							continue;
  						if (i >= this.dataGrid.getSize()[0])
  							continue;
  						float cellLength = (max - min) / (numberOfBins);
  						// float value = dataGrid.getAtIndex(i, j);
  						double value = multiChannelGrid.getChannel(c)
  								.getAtIndex(i, j);
  						int coord = (int) Math
  								.floor((value - min) / cellLength);
  						if (coord > numberOfBins) {
  							coord = numberOfBins;
  						}
  						if (coord < 0)
  							coord = 0;
  						histo[coord]++;
  					}
  				}
  
  				// histo[numberOfBins] = labelGrid.getAtIndex(x, y);
  				double[] singleVector = extractHistogramFeatures(histo);
  
  				for (int k = 0; k < HistoFeatures; k++) {
  					result[(c * HistoFeatures) + k] = singleVector[k];
  				}
  			}
  		} else {
  
  			result = new double[(HistoFeatures + 1)];
  			double[] histo = new double[numberOfBins + 1];
  
  			for (int j = y - patchSize; j <= y + patchSize; j++) {
  				if (j < 0)
  					continue;
  				if (j >= this.dataGrid.getSize()[1])
  					continue;
  				for (int i = x - patchSize; i <= x + patchSize; i++) {
  					if (i < 0)
  						continue;
  					if (i >= this.dataGrid.getSize()[0])
  						continue;
  					float cellLength = (max - min) / (numberOfBins);
  					float value = dataGrid.getAtIndex(i, j);
  					int coord = (int) Math.floor((value - min) / cellLength);
  					if (coord > numberOfBins) {
  						coord = numberOfBins;
  					}
  					if (coord < 0)
  						coord = 0;
  					histo[coord]++;
  				}
  			}
  			result = extractHistogramFeatures(histo);
  
  		}
  		result[result.length - 1] = labelGrid.getAtIndex(x, y);
  		return result;
  	}
  
  	/**
  	 * @return the numberOfBins
  	 */
  	public int getNumberOfBins() {
  		return numberOfBins;
  	}
  
  	/**
  	 * @param numberOfBins
  	 *            the numberOfBins to set
  	 */
  	public void setNumberOfBins(int numberOfBins) {
  		this.numberOfBins = numberOfBins;
  	}
  
  	/**
  	 * @return the patchSize
  	 */
  	public int getPatchSize() {
  		return patchSize;
  	}
  
  	/**
  	 * @param patchSize
  	 *            the patchSize to set
  	 */
  	public void setPatchSize(int patchSize) {
  		this.patchSize = patchSize;
  	}
  
  	/**
  	 * @return the min
  	 */
  	public float getMin() {
  		return min;
  	}
  
  	/**
  	 * @param min
  	 *            the min to set
  	 */
  	public void setMin(float min) {
  		this.min = min;
  	}
  
  	/**
  	 * @return the max
  	 */
  	public float getMax() {
  		return max;
  	}
  
  	/**
  	 * @param max
  	 *            the max to set
  	 */
  	public void setMax(float max) {
  		this.max = max;
  	}
  
  	@Override
  	public String getName() {
  		return "Patch-based Histogram Features";
  	}
  
  }
  