/*******************************************************************************
* Copyright (c) 2016 Weasis Team and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nicolas Roduit - initial API and implementation
*******************************************************************************/
package org.weasis.core.api.image.util;
import org.weasis.core.api.Messages;
import org.weasis.core.api.gui.util.MathUtil;
// A simple histogram class
public class BasicHist {
private static final String[] STATISTICS_LIST =
{ Messages.getString("BasicHist.pix"), Messages.getString("BasicHist.min"), Messages.getString("BasicHist.max"), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
Messages.getString("BasicHist.mean"), Messages.getString("BasicHist.median"), //$NON-NLS-1$ //$NON-NLS-2$
Messages.getString("BasicHist.thresh"), Messages.getString("BasicHist.std"), //$NON-NLS-1$ //$NON-NLS-2$
Messages.getString("BasicHist.skew"), Messages.getString("BasicHist.kurtosis"), //$NON-NLS-1$ //$NON-NLS-2$
Messages.getString("BasicHist.entropy") }; //$NON-NLS-1$
private int[] bins;
private int numBins;
private double lo;
private double hi;
private double range;
// The constructor will create an array of a given
// number of bins. The range of the histogram given
// by the upper and lower limit values.
public BasicHist(int numBins, double lo, double hi) {
this.numBins = numBins;
bins = new int[numBins];
this.lo = lo;
this.hi = hi;
range = hi - lo;
}
// Add an entry to a bin.
// Include if value is in the range
public boolean add(double x) {
if (x >= lo && x <= hi) {
double val = x - lo;
// Casting to int will round off to lower
// integer value.
int bin = (int) (numBins * (val / range - 0.00000001));
// Increment the corresponding bin.
bins[bin]++;
return true;
}
return false;
}
public void add(double[] x) {
for (int i = 0; i < x.length; i++) {
if (x[i] >= lo && x[i] <= hi) {
double val = x[i] - lo;
// Casting to int will round off to lower
// integer value.
int bin = (int) (numBins * (val / range));
// Increment the corresponding bin.
bins[bin]++;
}
}
}
/**
* add
*
* @param obj
* Object[]
*/
public void add(Object[] obj) {
if (obj != null && obj.length > 1 && obj[0] instanceof Number) {
for (int i = 0; i < obj.length; i++) {
double val = ((Number) obj[i]).doubleValue();
if (val >= lo && val <= hi) {
val = val - lo;
// Casting to int will round off to lower
// integer value.
int bin = (int) ((numBins - 1) * (val / range));
// Increment the corresponding bin.
bins[bin]++;
}
}
}
}
// Clear the histogram bins.
public void clear() {
for (int i = 0; i < numBins; i++) {
bins[i] = 0;
}
}
public int[] getBins() {
return bins;
}
public Integer[] getIntegerBins() {
Integer[] vals = new Integer[numBins];
for (int i = 0; i < numBins; i++) {
vals[i] = bins[i];
}
return vals;
}
public double getRange() {
return range;
}
public double getHi() {
return hi;
}
public double getLo() {
return lo;
}
public int[] getCummulative() {
int[] cml = new int[numBins];
int sum = 0;
for (int j = 0; j < numBins; j++) {
sum += bins[j];
cml[j] = sum;
}
return cml;
}
public static double[] getStatistics(int[] bins, double treshRatio, int offset) {
double[] stat = new double[STATISTICS_LIST.length];
if (bins != null && bins.length > 1) {
stat[1] = Double.MAX_VALUE;
stat[2] = -Double.MAX_VALUE;
for (int i = 0; i < bins.length; i++) {
double val = bins[i];
if (MathUtil.isDifferentFromZero(val) && i < stat[1]) {
stat[1] = i;
}
if (MathUtil.isDifferentFromZero(val) && i > stat[2]) {
stat[2] = i;
}
stat[0] += val;
stat[3] += val * (i + offset);
}
stat[1] += offset;
stat[2] += offset;
stat[3] /= stat[0];
stat[4] = medianBin(bins, (int) stat[0] / 2) + offset;
stat[5] = treshRatio;
double m2 = 0.0;
for (int i = 0; i < bins.length; i++) {
double factor = bins[i];
double val = (i + offset) - stat[3];
m2 += factor * Math.pow(val, 2); // variance
stat[7] += factor * Math.pow(val, 3); // skewness
stat[8] += factor * Math.pow(val, 4); // kurtosis
}
double variance = m2 / (stat[0] - 1); // variance
stat[6] = Math.sqrt(variance);
if (bins.length > 3 && variance > 10E-20) {
stat[7] = (stat[0] * stat[7]) / ((stat[0] - 1) * (stat[0] - 2) * stat[6] * variance);
stat[8] = (stat[0] * (stat[0] + 1) * stat[8] - 3 * m2 * m2 * (stat[0] - 1))
/ ((stat[0] - 1) * (stat[0] - 2) * (stat[0] - 3) * variance * variance);
} else {
stat[7] = 0.0;
stat[8] = 0.0;
}
stat[9] = getEntropy(bins, stat[0]);
}
return stat;
}
public static double getEntropy(int[] data, double nbPixels) {
double entropy = 0.0;
if (data == null || data.length < 1) {
return 0.0;
} else {
double log2 = Math.log(2.0);
for (int b = 0; b < data.length; b++) {
double p = data[b] / nbPixels;
if (MathUtil.isDifferentFromZero(p)) {
entropy -= p * (Math.log(p) / log2);
}
}
}
return entropy;
}
public static double medianBin(final int[] bin, int halfEntries) {
if (bin == null || bin.length < 1) {
return 0.0;
} else {
int sumBinEntries = 0;
int sum;
for (int i = 0; i < bin.length; i++) {
sum = sumBinEntries + bin[i];
// Check if bin crosses halfTotal point
if (sum >= halfEntries) {
// Scale linearly across the bin
int dif = halfEntries - sumBinEntries;
double frac = 0.0;
if (bin[i] > 0) {
frac = ((double) dif) / (double) bin[i];
}
return i + frac;
}
sumBinEntries = sum;
}
}
return 0.0;
}
}