org.jaitools.media.jai.classifiedstats.ClassifiedStatsOpImage Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jt-all Show documentation
Show all versions of jt-all Show documentation
Provides a single jar containing all JAITools modules which you can
use instead of including individual modules in your project. Note:
It does not include the Jiffle scripting language or Jiffle image
operator.
/*
* Copyright (c) 2009-2011, Daniele Romagnoli. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jaitools.media.jai.classifiedstats;
import java.awt.Rectangle;
import java.awt.image.RenderedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.media.jai.AreaOpImage;
import javax.media.jai.ImageLayout;
import javax.media.jai.NullOpImage;
import javax.media.jai.OpImage;
import javax.media.jai.ROI;
import javax.media.jai.iterator.RandomIter;
import javax.media.jai.iterator.RandomIterFactory;
import org.apache.commons.collections.keyvalue.MultiKey;
import org.jaitools.CollectionFactory;
import org.jaitools.numeric.Range;
import org.jaitools.numeric.Range.Type;
import org.jaitools.numeric.RangeUtils;
import org.jaitools.numeric.Statistic;
import org.jaitools.numeric.StreamingSampleStats;
/**
* Calculates image classified summary statistics for a data image.
*
* @see ClassifiedStatsDescriptor for Description of the algorithm and example
*
* @author Daniele Romagnoli, GeoSolutions S.A.S.
* @since 1.2
*/
public class ClassifiedStatsOpImage extends NullOpImage {
/**
* A simple object holding classifier properties:
* - a RandomIterator attached to the classifier Image
* - a boolean stating whether we need to check for noData on it
* - a noData value in case we need to check for it. In case the previous boolean
* is set to false, the noData value will be ignored.
*
* @author Daniele Romagnoli, GeoSolutions SAS
*/
private class ClassifierObject {
/**
* @param classifierIter
* @param checkForNoData
* @param noData
*/
public ClassifierObject(RandomIter classifierIter, boolean checkForNoData, int noData) {
this.classifierIter = classifierIter;
this.checkForNoData = checkForNoData;
this.noData = noData;
}
RandomIter classifierIter;
boolean checkForNoData;
int noData; //Classifiers are ALWAYS of integer type
}
private final Integer[] srcBands;
private final ROI roi;
/**
* Statistics to be computed
*/
private final Statistic[] stats;
/**
* basic image boundary properties allowing tiled computations
*/
private int imageWidth;
private int imageHeigth;
private int imageMinY;
private int imageMinX;
private int imageMaxX;
private int imageMaxY;
private int imageMinTileX;
private int imageMinTileY;
private int imageMaxTileY;
private int imageMaxTileX;
private int imageTileHeight;
private int imageTileWidth;
private final Rectangle dataImageBounds;
private final RenderedImage dataImage;
/**
* Images used for classifications
*/
private final RenderedImage[] classifierImages;
/**
* Pivot Images used for classifications
*/
private final RenderedImage[] pivotClassifierImages;
/**
* Optional array to specify which value of each classifer should be considered as NoData
* and then filter out from the computation, the pixels at those coordinates.
*
* Note that although classifier are always of integer types, we use double to allow specifying
* NaN as "unknown/unspecified" element
*/
private double[] noDataForClassifierImages;
/**
* Optional array to specify which value of each pivotClassifier should be considered as NoData
* and then filter out from the computation, the pixels at those coordinates
*
* Note that although pivot classifier are always of integer types, we use double to allow
* specifying NaN as "unknown/unspecified" element
*/
private double[] noDataForPivotClassifierImages;
/**
* Optional ranges to exclude/include values from/in statistics computations
*/
private final List> ranges;
/**
* Optional ranges to specify which values should be considered as NoData
* and then excluded from computations
*/
private final List> noDataRanges;
/** Compute separated statistics on ranges if true */
private final boolean rangeLocalStats;
/**
* Define whether provided ranges of values need to be included or excluded
* from statistics computations
*/
private Range.Type rangesType;
/**
* Constructor.
*
* @param dataImage
* a {@code RenderedImage} from which data values will be read.
*
* @param classifierImages
* a {@code RenderedImage}'s array of integral data type that
* defines the classification for which to calculate summary
* data.
* @param pivotClassifierImages
* an optional {@code RenderedImage}'s array of integral data type that
* defines the pivot classification for which to calculate summary
* data. Elements of this array are used to form group with the standard
* classifiers. As an instance, suppose the classifiers are [classifier1,
* classifier2] and the pivot classifiers are [pivot1, pivot2], then the
* stats will be computed on classifiers [pivot1, classifier1, classifier2]
* and [pivot2, classifier1, classifier2].
* @param config
* configurable attributes of the image (see {@link AreaOpImage}
* ).
*
* @param layout
* an optional {@code ImageLayout} object.
*
* @param stats
* an array of {@code Statistic} constants specifying the data
* required.
*
* @param bands
* the data image band to process.
*
* @param roi
* an optional {@code ROI} for data image masking.
*
* @param ranges
* an optional list of {@link Range} objects defining values to
* include or exclude (depending on {@code rangesType} from the
* calculations; may be {@code null} or empty
*
* @param rangesType
* specifies whether the {@code ranges} argument defines values
* to include or exclude
*
* @param rangeLocalStats
* if {@code true}, the statistics should be computed for ranges,
* separately.
*
* @param noDataRanges
* an optional list of {@link Range} objects defining values to
* treat as NODATA
* @param noDataClassifiers
* an optional array of Doubles defining values to
* treat as NODATA for the related classifierImage. Note that
* classifier images will always leverage on integer types
* (BYTE, INTEGER, SHORT, ...). Such noData are specified
* as Double to allow the users to provide NaN in case a NoData
* is unavailable for a specific classifierImage.
* @param noDataPivotClassifiers
* an optional array of Doubles defining values to
* treat as NODATA for the related pivotClassifierImage. Note that
* classifier images will always leverage on integer types
* (BYTE, INTEGER, SHORT, ...). Such noData are specified
* as Double to allow the users to provide NaN in case a NoData
* is unavailable for a specific pivotClassifierImage.
*
* @see ClassifiedStatsDescriptor
* @see Statistic
*/
public ClassifiedStatsOpImage(
final RenderedImage dataImage,
final RenderedImage[] classifierImages,
final RenderedImage[] pivotClassifierImages,
final Map, ?> config,
final ImageLayout layout,
final Statistic[] stats,
final Integer[] bands,
final ROI roi,
final Collection> ranges,
final Range.Type rangesType,
final boolean rangeLocalStats,
final Collection> noDataRanges,
final Double[] noDataClassifiers,
final Double[] noDataPivotClassifiers
) {
super(dataImage, layout, config, OpImage.OP_COMPUTE_BOUND);
this.dataImage = dataImage;
this.classifierImages = classifierImages;
this.pivotClassifierImages = pivotClassifierImages;
// Setting imagesParameters
this.imageWidth = dataImage.getWidth();
this.imageHeigth = dataImage.getHeight();
this.imageTileWidth = Math.min(dataImage.getTileWidth(), imageWidth);
this.imageTileHeight = Math.min(dataImage.getTileHeight(), imageHeigth);
this.imageMinY = dataImage.getMinY();
this.imageMinX = dataImage.getMinX();
this.imageMaxX = imageMinX + imageWidth - 1;
this.imageMaxY = imageMinY + imageHeigth - 1;
this.imageMinTileX = dataImage.getMinTileX();
this.imageMinTileY = dataImage.getMinTileY();
this.imageMaxTileX = imageMinTileX + dataImage.getNumXTiles();
this.imageMaxTileY = imageMinTileY + dataImage.getNumYTiles();
dataImageBounds = new Rectangle(imageMinX, imageMinY, imageWidth, imageHeigth);
this.stats = new Statistic[stats.length];
System.arraycopy(stats, 0, this.stats, 0, stats.length);
this.srcBands = new Integer[bands.length];
System.arraycopy(bands, 0, this.srcBands, 0, bands.length);
this.roi = roi;
// --------------------------------------------
// Ranges initialization
// --------------------------------------------
this.rangeLocalStats = rangeLocalStats;
this.ranges = CollectionFactory.list();
this.rangesType = rangesType;
if (ranges != null && !ranges.isEmpty()) {
// copy the ranges defensively
for (Range r : ranges) {
this.ranges.add(new Range(r));
}
}
// --------------------------------------------
// NoData initialization
// --------------------------------------------
this.noDataRanges = CollectionFactory.list();
if (noDataRanges != null && !noDataRanges.isEmpty()) {
// copy the ranges defensively
for (Range r : noDataRanges) {
this.noDataRanges.add(new Range(r));
}
}
if (noDataClassifiers != null){
this.noDataForClassifierImages = new double[noDataClassifiers.length];
for (int i = 0; i < noDataClassifiers.length; i++){
this.noDataForClassifierImages[i] = noDataClassifiers[i];
}
}
if (noDataPivotClassifiers!= null){
this.noDataForPivotClassifierImages = new double[noDataPivotClassifiers.length];
for (int i = 0; i < noDataPivotClassifiers.length; i++){
this.noDataForPivotClassifierImages[i] = noDataPivotClassifiers[i];
}
}
}
/**
* Delegates calculation of statistics to either
* {@linkplain #compileRangeStatistics()} or
* {@linkplain #compileClassifiedStatistics()}.
*
* @return the results as a new instance of {@code ClassifiedStats}
*/
synchronized ClassifiedStats compileStatistics() {
ClassifiedStats classifiedStats = null;
// --------------------------------
//
// Init classifiers and iterators
//
// --------------------------------
final RandomIter dataIter = RandomIterFactory.create(dataImage, dataImageBounds);
final int numClassifiers = classifierImages.length;
final int numPivotClassifiers = pivotClassifierImages != null ? pivotClassifierImages.length : 0;
// Standard Classifiers
final ClassifierObject[] classifiers = new ClassifierObject[numClassifiers];
for (int i = 0; i < numClassifiers; i++) {
final RandomIter classifierIter = RandomIterFactory.create(classifierImages[i], dataImageBounds);
final boolean checkForNoData = (noDataForClassifierImages != null && !Double.isNaN(noDataForClassifierImages[i])) ?
true : false;
final int noDataClassifierValue = checkForNoData ? (int)noDataForClassifierImages[i] : 0;
classifiers[i] = new ClassifierObject(classifierIter, checkForNoData, noDataClassifierValue);
}
//Pivot Classifiers
final ClassifierObject[] pivotClassifiers = numPivotClassifiers > 0 ? new ClassifierObject[numPivotClassifiers] : null;
for (int i = 0; i < numPivotClassifiers; i++) {
final RandomIter classifierIter = RandomIterFactory.create(pivotClassifierImages[i], dataImageBounds);
final boolean checkForNoData = (noDataForPivotClassifierImages != null && !Double.isNaN(noDataForPivotClassifierImages[i])) ?
true : false;
final int noDataClassifierValue = checkForNoData ? (int)noDataForPivotClassifierImages[i] : 0;
pivotClassifiers[i] = new ClassifierObject(classifierIter, checkForNoData, noDataClassifierValue);
}
// --------------------------------
//
// Compute statistics
//
// --------------------------------
if (!rangeLocalStats) {
classifiedStats = compileClassifiedStatistics(dataIter, classifiers, pivotClassifiers);
} else {
classifiedStats = compileLocalRangeStatistics(dataIter, classifiers);
}
// --------------------------------
//
// Closing/disposing the iterators
//
// --------------------------------
dataIter.done();
for (int i = 0; i < numClassifiers; i++) {
classifiers[i].classifierIter.done();
}
for (int i = 0; i < numPivotClassifiers; i++) {
pivotClassifiers[i].classifierIter.done();
}
return classifiedStats;
}
/**
* Called by {@link #compileClassifiedStatistics()} to lazily create a
* {@link StreamingSampleStats} object for each classifier. The new object
* is added to the provided {@code resultsPerBand} {@code Map}.
*
* @param resultsPerBand
* {@code Map} of results by classifier
* @param classifierKey
* the classifier key referring to this statistic
* @param rangesType
* the range type
* @param ranges
* a List of Range to be added to these stats.
*
*
* @return a new {@code StreamingSampleStats} object
*/
protected StreamingSampleStats setupStats(Map resultsPerBand,
MultiKey classifierKey, Range.Type rangesType, List> ranges) {
StreamingSampleStats sampleStats = new StreamingSampleStats(rangesType);
for (Range r : ranges) {
sampleStats.addRange(r);
}
for (Range r : noDataRanges) {
sampleStats.addNoDataRange(r);
}
sampleStats.setStatistics(stats);
resultsPerBand.put(classifierKey, sampleStats);
return sampleStats;
}
/**
* Used to calculate statistics against classifier rasters.
* @param dataIter
* the input image data iterator
* @param classifiers
* the classifiers objects for the classified stat
*
* @return the results as a {@code ClassifiedStats} instance
*/
private ClassifiedStats compileClassifiedStatistics(
final RandomIter dataIter,
ClassifierObject[] classifiers,
ClassifierObject[] pivotClassifiers) {
ClassifiedStats classifiedStats = new ClassifiedStats();
Map>> results = CollectionFactory.sortedMap();
final int numPivots = pivotClassifiers != null ? pivotClassifiers.length : 0;
for (Integer srcBand : srcBands) {
// If pivots are present, grouping the results by pivot
if (numPivots > 0){
List