//----------------------------------------------------------------------------//
// //
// H i s t o g r a m //
// //
//----------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr"> //
// Copyright © Hervé Bitteur and others 2000-2013. All rights reserved. //
// This software is released under the GNU General Public License. //
// Goto http://kenai.com/projects/audiveris to report bugs or suggestions. //
//----------------------------------------------------------------------------//
// </editor-fold>
package omr.math;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Class {@code Histogram} is an histogram implementation which handles
* integer counts in buckets, the buckets identities being values of
* type K.
*
* @param <K> the precise type for histogram buckets
*
* @author Hervé Bitteur
*/
public class Histogram<K extends Number>
{
//~ Instance fields --------------------------------------------------------
/** To sort peaks by decreasing value */
public final Comparator<PeakEntry<K>> reversePeakComparator = new Comparator<PeakEntry<K>>()
{
@Override
public int compare (PeakEntry<K> e1,
PeakEntry<K> e2)
{
// Put largest value first!
return Double.compare(e2.getValue(), e1.getValue());
}
};
/** To sort double peaks by decreasing value */
public final Comparator<PeakEntry<Double>> reverseDoublePeakComparator = new Comparator<PeakEntry<Double>>()
{
@Override
public int compare (PeakEntry<Double> e1,
PeakEntry<Double> e2)
{
// Put largest value first!
return Double.compare(e2.getValue(), e1.getValue());
}
};
/** To sort double peaks by decreasing value */
public final Comparator<MaxEntry<K>> reverseMaxComparator = new Comparator<MaxEntry<K>>()
{
@Override
public int compare (MaxEntry<K> e1,
MaxEntry<K> e2)
{
// Put largest value first!
return Double.compare(e2.getValue(), e1.getValue());
}
};
/**
* Underlying map:
* - K for the type of entity to be accumulated
* - Integer for the cumulated number in each bucket
*/
protected final SortedMap<K, Integer> map = new TreeMap<>();
/** Total count */
protected int totalCount = 0;
//~ Constructors -----------------------------------------------------------
//-----------//
// Histogram //
//-----------//
/**
* Creates a new Histogram object, with no pre-defined range of buckets
*/
public Histogram ()
{
}
//-----------//
// Histogram //
//-----------//
/**
* Creates a new Histogram object, with pre-definition of the bucket range
*
* @param first the first bucket of the foreseen range
* @param last the last bucket of the foreseen range
*/
public Histogram (K first,
K last)
{
map.put(first, 0);
map.put(last, 0);
}
//~ Methods ----------------------------------------------------------------
//-----------//
// bucketSet //
//-----------//
public Set<K> bucketSet ()
{
return map.keySet();
}
//-------//
// clear //
//-------//
public void clear ()
{
map.clear();
totalCount = 0;
}
//------------//
// dataString //
//------------//
public String dataString ()
{
StringBuilder sb = new StringBuilder("[");
boolean first = true;
for (Map.Entry<K, Integer> entry : entrySet()) {
sb.append(
String.format(
"%s%s:%d",
first ? "" : " ",
entry.getKey().toString(),
entry.getValue()));
first = false;
}
sb.append("]");
return sb.toString();
}
//----------//
// entrySet //
//----------//
public Set<Map.Entry<K, Integer>> entrySet ()
{
return map.entrySet();
}
//-------------//
// firstBucket //
//-------------//
public K firstBucket ()
{
return map.firstKey();
}
//----------//
// getCount //
//----------//
/**
* Report the count of specified bucket
*
* @param bucket the bucket of interest
* @return the bucket count (zero for any empty bucket)
*/
public int getCount (K bucket)
{
Integer count = map.get(bucket);
if (count == null) {
return 0;
} else {
return count;
}
}
//----------//
// getPeaks //
//----------//
/**
* Report the sequence of bucket peaks whose count is equal to or
* greater than the specified minCount value.
*
* @param minCount the desired minimum count value
* @return the (perhaps empty but not null) sequence of peaks of buckets
*/
public List<PeakEntry<Double>> getDoublePeaks (int minCount)
{
final List<PeakEntry<Double>> peaks = new ArrayList<>();
K start = null;
K stop = null;
K best = null;
Integer bestCount = null;
boolean isAbove = false;
for (Entry<K, Integer> entry : map.entrySet()) {
if (entry.getValue() >= minCount) {
if ((bestCount == null) || (bestCount < entry.getValue())) {
best = entry.getKey();
bestCount = entry.getValue();
}
if (isAbove) { // Above -> Above
stop = entry.getKey();
} else { // Below -> Above
stop = start = entry.getKey();
isAbove = true;
}
} else {
if (isAbove) { // Above -> Below
peaks.add(
new PeakEntry<>(
createDoublePeak(start, best, stop, minCount),
(double) bestCount / totalCount));
stop = start = best = null;
bestCount = null;
isAbove = false;
} else { // Below -> Below
}
}
}
// Last range
if (isAbove) {
peaks.add(
new PeakEntry<>(
createDoublePeak(start, best, stop, minCount),
(double) bestCount / totalCount));
}
// Sort by decreasing count values
Collections.sort(peaks, reverseDoublePeakComparator);
return peaks;
}
//----------------//
// getLocalMaxima //
//----------------//
/**
* Report the local maximum points, sorted by decreasing count
*
* @return the (count-based) sorted sequence of local maxima
*/
public List<MaxEntry<K>> getLocalMaxima ()
{
final List<MaxEntry<K>> maxima = new ArrayList<>();
K prevKey = null;
int prevValue = 0;
boolean growing = false;
for (Entry<K, Integer> entry : map.entrySet()) {
K key = entry.getKey();
int value = entry.getValue();
if (prevKey != null) {
if (value >= prevValue) {
growing = true;
} else {
if (growing) {
// End of a local max
maxima.add(
new MaxEntry<>(
prevKey,
prevValue / (double) totalCount));
}
growing = false;
}
}
prevKey = key;
prevValue = value;
}
// Sort by decreasing count values
Collections.sort(maxima, reverseMaxComparator);
return maxima;
}
//--------------//
// getMaxBucket //
//--------------//
/**
* Report the bucket with highest count
*
* @return the most popular bucket
*/
public K getMaxBucket ()
{
int max = Integer.MIN_VALUE;
K bucket = null;
for (Map.Entry<K, Integer> entry : map.entrySet()) {
if (entry.getValue() > max) {
max = entry.getValue();
bucket = entry.getKey();
}
}
return bucket;
}
//-------------//
// getMaxCount //
//-------------//
/**
* Report the highest count among all buckets
*
* @return the largest count value
*/
public int getMaxCount ()
{
int max = Integer.MIN_VALUE;
for (Map.Entry<K, Integer> entry : map.entrySet()) {
max = Math.max(max, entry.getValue());
}
return max;
}
//------------//
// getMaximum //
//------------//
/**
* Report the maximum entry in this histogram
*
* @return the maximum entry (key & value)
*/
public Map.Entry<K, Integer> getMaximum ()
{
Map.Entry<K, Integer> maximum = null;
for (Map.Entry<K, Integer> entry : map.entrySet()) {
int value = entry.getValue();
if ((maximum == null) || (value > maximum.getValue())) {
maximum = entry;
}
}
return maximum;
}
//----------//
// getPeaks //
//----------//
/**
* Report the sequence of bucket peaks whose count is equal to or greater
* than the specified minCount value
*
* @param minCount the desired minimum count value
* @param absolute if true, absolute counts values are reported in peaks,
* otherwise relative counts to total histogram are used
* @param sorted if true, the reported sequence is sorted by decreasing
* count value, otherwise it is reported as naturally found along K data.
* @return the (perhaps empty but not null) sequence of peaks of buckets
*/
public List<PeakEntry<K>> getPeaks (int minCount,
boolean absolute,
boolean sorted)
{
final List<PeakEntry<K>> peaks = new ArrayList<>();
K start = null;
K stop = null;
K best = null;
Integer bestCount = null;
boolean isAbove = false;
for (Entry<K, Integer> entry : map.entrySet()) {
if (entry.getValue() >= minCount) {
if ((bestCount == null) || (bestCount < entry.getValue())) {
best = entry.getKey();
bestCount = entry.getValue();
}
if (isAbove) { // Above -> Above
stop = entry.getKey();
} else { // Below -> Above
stop = start = entry.getKey();
isAbove = true;
}
} else {
if (isAbove) { // Above -> Below
peaks.add(
new PeakEntry<>(
new Peak<>(start, best, stop),
absolute ? bestCount : ((double) bestCount / totalCount)));
stop = start = best = null;
bestCount = null;
isAbove = false;
} else { // Below -> Below
}
}
}
// Last range
if (isAbove) {
peaks.add(
new PeakEntry<>(
new Peak<>(start, best, stop),
absolute ? bestCount : ((double) bestCount / totalCount)));
}
// Sort by decreasing count values?
if (sorted) {
Collections.sort(peaks, reversePeakComparator);
}
return peaks;
}
//----------------//
// getQuorumValue //
//----------------//
/**
* Based on the current population, report the quorum value coresponding
* to the provided quorum ratio
*
* @param quorumRatio quorum specified as a percentage of total count
* @return the quorum value
*/
public int getQuorumValue (double quorumRatio)
{
return (int) Math.rint(quorumRatio * getTotalCount());
}
//---------------//
// getTotalCount //
//---------------//
/**
* Report the total counts of all buckets
*
* @return the sum of all counts
*/
public int getTotalCount ()
{
return totalCount;
}
//---------------//
// increaseCount //
//---------------//
public void increaseCount (K bucket,
int delta)
{
Integer count = map.get(bucket);
if (count == null) {
map.put(bucket, delta);
} else {
map.put(bucket, count + delta);
}
totalCount += delta;
}
//------------//
// lastBucket //
//------------//
public K lastBucket ()
{
return map.lastKey();
}
//-------//
// print //
//-------//
public void print (PrintStream stream)
{
stream.println(dataString());
}
//------//
// size //
//------//
/**
* Report the number of non empty buckets
*
* @return the number of non empty buckets
*/
public int size ()
{
return map.size();
}
//----------//
// toString //
//----------//
@Override
public String toString ()
{
StringBuilder sb = new StringBuilder("{");
sb.append(getClass().getSimpleName());
sb.append(
String.format(
" %s-%s",
(firstBucket() != null) ? firstBucket().toString() : "",
(lastBucket() != null) ? lastBucket().toString() : ""));
sb.append(" size:")
.append(size());
sb.append(" ")
.append(dataString());
sb.append("}");
return sb.toString();
}
//--------//
// values //
//--------//
public Collection<Integer> values ()
{
return map.values();
}
//------------------//
// createDoublePeak //
//------------------//
private DoublePeak createDoublePeak (K first,
K best,
K second,
int count)
{
// Use interpolation for more accurate data on first & second
double preciseFirst = first.doubleValue();
K prevKey = prevKey(first);
if (prevKey != null) {
preciseFirst = preciseKey(prevKey, first, count);
}
double preciseSecond = second.doubleValue();
K nextKey = nextKey(second);
if (nextKey != null) {
preciseSecond = preciseKey(second, nextKey, count);
}
return new DoublePeak(preciseFirst, best.doubleValue(), preciseSecond);
}
//---------//
// nextKey //
//---------//
private K nextKey (K key)
{
boolean found = false;
for (K k : map.keySet()) {
if (found) {
return k;
} else if (key.equals(k)) {
found = true;
}
}
return null;
}
//------------//
// preciseKey //
//------------//
private double preciseKey (K prev,
K next,
int count)
{
// Use interpolation for accurate data between prev & next keys
double prevCount = getCount(prev);
double nextCount = getCount(next);
return ((prev.doubleValue() * (nextCount - count))
+ (next.doubleValue() * (count - prevCount))) / (nextCount
- prevCount);
}
//---------//
// prevKey //
//---------//
private K prevKey (K key)
{
K prev = null;
for (K k : map.keySet()) {
if (key.equals(k)) {
return prev;
} else {
prev = k;
}
}
return null;
}
//~ Inner Classes ----------------------------------------------------------
//------------//
// DoublePeak //
//------------//
public static class DoublePeak
extends Peak<Double>
{
//~ Constructors -------------------------------------------------------
private DoublePeak (double first,
double best,
double second)
{
super(first, best, second);
}
}
//----------//
// MaxEntry //
//----------//
public static class MaxEntry<K extends Number>
{
//~ Instance fields ----------------------------------------------------
/** Key at local maximum */
private final K key;
/** Related count (normalized by total histogram count) */
private final double value;
//~ Constructors -------------------------------------------------------
public MaxEntry (K key,
double value)
{
this.key = key;
this.value = value;
}
//~ Methods ------------------------------------------------------------
/**
* @return the key
*/
public K getKey ()
{
return key;
}
/**
* @return the value
*/
public double getValue ()
{
return value;
}
@Override
public String toString ()
{
return getKey() + "=" + (float) getValue();
}
}
//------//
// Peak //
//------//
public static class Peak<K extends Number>
{
//~ Instance fields ----------------------------------------------------
/** Value at beginning of range */
public final K first;
/** Value at highest point in range */
public final K best;
/** Value at end of range */
public final K second;
//~ Constructors -------------------------------------------------------
public Peak (K first,
K best,
K second)
{
this.first = first;
this.best = best;
this.second = second;
}
//~ Methods ------------------------------------------------------------
@Override
public String toString ()
{
return "(" + first.floatValue() + "," + best.floatValue() + ","
+ second.floatValue() + ")";
}
}
//-----------//
// PeakEntry //
//-----------//
public static class PeakEntry<K extends Number>
{
//~ Instance fields ----------------------------------------------------
/** The peak data */
private final Peak<K> key;
/** Count at best value (normalized by total histogram count) */
private final double value;
//~ Constructors -------------------------------------------------------
public PeakEntry (Peak<K> key,
double value)
{
this.key = key;
this.value = value;
}
//~ Methods ------------------------------------------------------------
/**
* Returns the key.
*
* @return the key
*/
public Peak<K> getKey ()
{
return key;
}
/**
* Returns the value associated with the key.
*
* @return the value associated with the key
*/
public double getValue ()
{
return value;
}
@Override
public String toString ()
{
return key + "=" + (float) value;
}
}
}