/* JAI-Ext - OpenSource Java Advanced Image Extensions Library * http://www.geo-solutions.it/ * Copyright 2014 GeoSolutions * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package it.geosolutions.jaiext.stats; import com.google.common.util.concurrent.AtomicDouble; import it.geosolutions.jaiext.range.Range; import it.geosolutions.jaiext.range.RangeFactory; /** * This subclass of {@link Statistics} is used for calculating the Histogram or the Mode of an image. These 2 operations are almost the same, the * difference is only at the final step when the histogram returns an array containing the number of pixels for every bin while the mode returns only * the most populated bean. This operation is achieved with the help of an AtomicDouble array, for avoiding thread-safety issues. There is no * statistic accumulation because multiple Histogram objects could bring to an {@link OutOfMemoryError}. */ public class HistogramMode extends Statistics { /** Boolean indicating if Histogram operation must be performed */ private final boolean histogramStat; /** Array number of bins */ private final int numBins; /** Range of all the bins array */ private final Range interval; /** Size of one bin */ private final double binInterval; /** Minimum bound of the array */ private final double minBound; /** Array containing all the bins */ private final AtomicDouble[] bins; HistogramMode(int numBins, double minBound, double maxBound, boolean histogramStat) { // Setting of the parameters this.histogramStat = histogramStat; this.numBins = numBins; // If the array bounds are infinite, the half of minimum and maximum values are taken if (minBound == Double.NEGATIVE_INFINITY) { minBound = -Double.MAX_VALUE / 2; } if (maxBound == Double.POSITIVE_INFINITY) { maxBound = Double.MAX_VALUE / 2; } this.interval = RangeFactory.create(minBound, true, maxBound, false, false); this.binInterval = (maxBound - minBound) / numBins; this.minBound = minBound; // Creation of the bin array this.bins = new AtomicDouble[numBins]; for (int i = 0; i < numBins; i++) { bins[i] = new AtomicDouble(0); } // Definition of the statsType if (histogramStat) { this.type = StatsType.HISTOGRAM; } else { this.type = StatsType.MODE; } } @Override public void addSample(double sample) { samples++; if (interval.contains(sample)) { // Selection of the index int index = getIndex(sample); // Update of the bin count bins[index].addAndGet(1); } } @Override protected void accumulateStats(Statistics stats) { throw new UnsupportedOperationException("Histogram statistics cannot be accumulated"); } @Override public Object getResult() { if (histogramStat) { // If the operation is Histogram, the result is returned as a double array double[] array = new double[numBins]; for (int i = 0; i < numBins; i++) { array[i] = bins[i].doubleValue(); } return array; } else { // If the operation is Mode, the most present value is returned double max = 0; int indexMax = 0; for (int i = 0; i < numBins; i++) { if (bins[i].doubleValue() > max) { max = bins[i].doubleValue(); indexMax = i; } } if (max == 0) { return indexMax * 1.0d; } else { return indexMax + minBound; } } } @Override public Long getNumSamples() { return Long.valueOf(samples); } @Override protected synchronized void clearStats() { // All the bins are set to 0 for (int i = 0; i < numBins; i++) { bins[i] = new AtomicDouble(0); } } /** Private method for calculating the bin-index associated to the sample */ private int getIndex(double sample) { int index = (int) ((sample - minBound) / binInterval); return index; } }