All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jaitools.media.jai.zonalstats.ZonalStatsOpImage Maven / Gradle / Ivy

Go to download

Calculates summary statistics, optionally within zones defined by a zone image, for values in a data image

The newest version!
/*
 * Copyright 2009-2010 Michael Bedward
 *
 * This file is part of jai-tools.
 *
 * jai-tools is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * jai-tools is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with jai-tools.  If not, see .
 *
 */

package jaitools.media.jai.zonalstats;

import jaitools.CollectionFactory;
import jaitools.numeric.Range;
import jaitools.numeric.RangeUtils;
import jaitools.numeric.Statistic;
import jaitools.numeric.StreamingSampleStats;

import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.RenderedImage;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;

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 javax.media.jai.iterator.RectIter;
import javax.media.jai.iterator.RectIterFactory;

/**
 * Calculates image summary statistics for a data image within zones defined by
 * a integral valued zone image. If a zone image is not provided all data image
 * pixels are treated as being in the same zone (zone 0).
 *
 * @see ZonalStatsDescriptor Description of the algorithm and example
 *
 * @author Michael Bedward
 * @author Andrea Antonello
 * @author Daniele Romagnoli, GeoSolutions S.A.S.
 * @since 1.0
 * @source $URL: https://jai-tools.googlecode.com/svn/tags/1.0.1/operator/zonalstats/src/main/java/jaitools/media/jai/zonalstats/ZonalStatsOpImage.java $
 * @version $Id: ZonalStatsOpImage.java 1176 2010-04-16 11:14:08Z michael.bedward $
 */
public class ZonalStatsOpImage extends NullOpImage {

    private final Integer[] srcBands;

    private final ROI roi;

    private final Statistic[] stats;

    private final RenderedImage dataImage;
    private final Rectangle dataImageBounds;
    private final RenderedImage zoneImage;
    private final AffineTransform zoneTransform;
    
    /** 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;

    private SortedSet zones;


    /**
     * Constructor.
     *
     * @param dataImage a {@code RenderedImage} from which data values will be read.
     *
     * @param zoneImage an optional {@code RenderedImage} of integral data type that defines
     *        the zones for which to calculate summary data.
     * 
     * @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 zoneTransform
     * 
     * @param excludedRanges a {@link List} of {@link Range}s, that will be filtered out
     *        of the process. This means that values inside the supplied ranges 
     *        will not be considered as valid and discarded.
     * 
     * @see ZonalStatsDescriptor
     * @see Statistic
     */
    public ZonalStatsOpImage(RenderedImage dataImage, RenderedImage zoneImage, 
            Map config,
            ImageLayout layout,
            Statistic[] stats, 
            Integer[] bands,
            ROI roi,
            AffineTransform zoneTransform,
            List> excludedRanges) {
    	this (dataImage, zoneImage, config, layout, stats, bands, roi, zoneTransform, excludedRanges, Range.Type.EXCLUDE, false, null);
    }
    
    /**
     * Constructor.
     *
     * @param dataImage a {@code RenderedImage} from which data values will be read.
     *
     * @param zoneImage an optional {@code RenderedImage} of integral data type that defines
     *        the zones for which to calculate summary data.
     * 
     * @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 zoneTransform
     * 
     * @param ranges a {@link List} of {@link Range}s, that will be filtered out/in
     *        of the process. This means that values inside the supplied ranges 
     *        will be considered as invalid/valid and discarded/accepted.
     *        
     * @param rangesType specify if the provided ranges argument should be considered 
     * 		  as Included or Excluded. See {@link Range.Type}.
     * 
     * @param rangeLocalStats if {@code true}, the statistics should be computed for ranges, 
     * 		  separately. 
     *        
     * 
     * @see ZonalStatsDescriptor
     * @see Statistic
     */
    public ZonalStatsOpImage(RenderedImage dataImage, RenderedImage zoneImage, 
            Map config,
            ImageLayout layout,
            Statistic[] stats, 
            Integer[] bands,
            ROI roi,
            AffineTransform zoneTransform,
            List> ranges,
            Range.Type rangesType,
            final boolean rangeLocalStats,
            List> noDataRanges) {

        super(dataImage, layout, config, OpImage.OP_COMPUTE_BOUND);

        this.dataImage = dataImage;
        this.zoneImage = zoneImage;

        dataImageBounds = new Rectangle(
        		dataImage.getMinX(), dataImage.getMinY(),
        		dataImage.getWidth(), dataImage.getHeight());

        this.stats = stats;
        this.srcBands = bands;
        this.roi = roi;
        this.zoneTransform = zoneTransform;
        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));
            }
        }
        
        this.noDataRanges = CollectionFactory.list();
        if (noDataRanges != null && !noDataRanges.isEmpty()) {
            
            // copy the ranges defensively
            for (Range r : noDataRanges) {
                this.noDataRanges.add(new Range(r));
            }
        }
    }

    /**
     * Compiles the set of zone ID values from the zone image. Note, we are
     * assuming that the zone values are in band 0.
     * 

* If a zone image wasn't provided we treat all data image pixels as * belonging to zone 0. */ private void buildZoneList() { zones = CollectionFactory.sortedSet(); if (zoneImage != null) { RectIter iter = RectIterFactory.create(zoneImage, null); do { do { zones.add(iter.getSample()); } while (!iter.nextPixelDone()); iter.startPixels(); } while (!iter.nextLineDone()); } else { zones.add(0); } } /** * Delegates calculation of statistics to either {@linkplain #compileZonalStatistics()} * or {@linkplain #compileUnzonedStatistics()}. * * @return the results as a new instance of {@code ZonalStats} */ private synchronized ZonalStats compileStatistics() { if (zoneImage != null) { return compileZonalStatistics(); } else { if (!rangeLocalStats) return compileUnzonedStatistics(); else return compileRangeStatistics(); } } /** * Used to calculate statistics when a zone image was provided. * * @return the results as a {@code ZonalStats} instance */ private ZonalStats compileZonalStatistics() { buildZoneList(); Map> results = CollectionFactory.sortedMap(); for( Integer srcBand : srcBands ) { Map resultsPerBand = CollectionFactory.sortedMap(); results.put(srcBand, resultsPerBand); for( Integer zone : zones ) { StreamingSampleStats sampleStats = new StreamingSampleStats(Range.Type.EXCLUDE); for (Range r : ranges) { sampleStats.addRange(r); } for (Range r : noDataRanges) { sampleStats.addNoDataRange(r); } sampleStats.setStatistics(stats); resultsPerBand.put(zone, sampleStats); } } final double[] sampleValues = new double[dataImage.getSampleModel().getNumBands()]; RectIter dataIter = RectIterFactory.create(dataImage, null); if (zoneTransform == null) { // Identity transform assumed RectIter zoneIter = RectIterFactory.create(zoneImage, dataImageBounds); int y = dataImage.getMinY(); do { int x = dataImage.getMinX(); do { if (roi == null || roi.contains(x, y)) { dataIter.getPixel(sampleValues); for( Integer band : srcBands ) { Map resultPerBand = results.get(band); int zone = zoneIter.getSample(); resultPerBand.get(zone).offer(sampleValues[band]); } } zoneIter.nextPixelDone(); // safe call x++; } while( !dataIter.nextPixelDone() ); dataIter.startPixels(); zoneIter.startPixels(); zoneIter.nextLineDone(); // safe call } while( !dataIter.nextLineDone() ); } else { final RandomIter zoneIter = RandomIterFactory.create(zoneImage, dataImageBounds); final Point2D.Double dataPos = new Point2D.Double(); final Point2D.Double zonePos = new Point2D.Double(); dataPos.y = dataImage.getMinY(); do { dataPos.x = dataImage.getMinX(); do { if (roi == null | roi.contains(dataPos)) { dataIter.getPixel(sampleValues); zoneTransform.transform(dataPos, zonePos); for (Integer band : srcBands) { Map resultPerBand = results.get(band); int zone = zoneIter.getSample((int) zonePos.x, (int) zonePos.y, 0); resultPerBand.get(zone).offer(sampleValues[band]); } } dataPos.x++; } while( !dataIter.nextPixelDone() ); dataIter.startPixels(); dataPos.y++; } while( !dataIter.nextLineDone() ); } ZonalStats zs = new ZonalStats(); for( Integer band : srcBands ) { for( Integer zone : zones ) { zs.setResults(band, zone, results.get(band).get(zone)); } } return zs; } /** * Used to calculate statistics when no zone image was provided. * * @return the results as a {@code ZonalStats} instance */ private ZonalStats compileUnzonedStatistics() { buildZoneList(); Integer zoneID = zones.first(); // create the stats final StreamingSampleStats sampleStatsPerBand[] = new StreamingSampleStats[srcBands.length]; for (int index = 0; index < srcBands.length; index++) { final StreamingSampleStats sampleStats = new StreamingSampleStats(rangesType); for (Range r : ranges) { sampleStats.addRange(r); } for (Range r : noDataRanges) { sampleStats.addNoDataRange(r); } sampleStats.setStatistics(stats); sampleStatsPerBand[index] = sampleStats; } final double[] sampleValues = new double[dataImage.getSampleModel().getNumBands()]; RectIter dataIter = RectIterFactory.create(dataImage, null); int y = dataImage.getMinY(); do { int x = dataImage.getMinX(); do { if (roi == null || roi.contains(x, y)) { dataIter.getPixel(sampleValues); for (int index = 0; index < srcBands.length; index++) { final double value = sampleValues[srcBands[index]]; sampleStatsPerBand[index].offer(value); } } x++; } while (!dataIter.nextPixelDone() ); dataIter.startPixels(); y++; } while (!dataIter.nextLineDone() ); // get the results final ZonalStats zs = new ZonalStats(); for (int index = 0; index < srcBands.length; index++) { final StreamingSampleStats sampleStats = sampleStatsPerBand[index]; List inclRanges = null; if (ranges != null && !ranges.isEmpty()){ switch(rangesType){ case INCLUDE: inclRanges = CollectionFactory.list(); inclRanges.addAll(ranges); break; case EXCLUDE: inclRanges = CollectionFactory.list(); List> incRanges = RangeUtils.createComplement(RangeUtils.sort(ranges)); inclRanges.addAll(incRanges); break; } } zs.setResults(srcBands[index], zoneID, sampleStats, inclRanges); } return zs; } /** * Used to calculate statistics when range local statistics are required. * * @return the results as a {@code ZonalStats} instance */ private ZonalStats compileRangeStatistics() { buildZoneList(); final Integer zoneID = zones.first(); final ZonalStats zs = new ZonalStats(); List localRanges = null; switch (rangesType){ case EXCLUDE: List> inRanges = RangeUtils.createComplement(RangeUtils.sort(ranges)); localRanges = CollectionFactory.list(); localRanges.addAll(inRanges); break; case INCLUDE: localRanges = CollectionFactory.list(); localRanges.addAll(ranges); break; case UNDEFINED: throw new UnsupportedOperationException("Unable to compute range local statistics on UNDEFINED ranges type"); } for (Range range: localRanges){ // create the stats final StreamingSampleStats sampleStatsPerBand[] = new StreamingSampleStats[srcBands.length]; for (int index = 0; index < srcBands.length; index++) { final StreamingSampleStats sampleStats = new StreamingSampleStats(rangesType); sampleStats.addRange(range); for (Range noDataRange: noDataRanges){ sampleStats.addNoDataRange(noDataRange); } sampleStats.setStatistics(stats); sampleStatsPerBand[index] = sampleStats; } final double[] sampleValues = new double[dataImage.getSampleModel().getNumBands()]; RectIter dataIter = RectIterFactory.create(dataImage, null); int y = dataImage.getMinY(); do { int x = dataImage.getMinX(); do { if (roi == null || roi.contains(x, y)) { dataIter.getPixel(sampleValues); for (int index = 0; index < srcBands.length; index++) { final double value = sampleValues[srcBands[index]]; sampleStatsPerBand[index].offer(value); } } x++; } while (!dataIter.nextPixelDone() ); dataIter.startPixels(); y++; } while( !dataIter.nextLineDone() ); // get the results for (int index = 0; index < srcBands.length; index++) { StreamingSampleStats sampleStats = sampleStatsPerBand[index]; List resultRanges = CollectionFactory.list(); resultRanges.add(range); zs.setResults(srcBands[index], zoneID, sampleStats, resultRanges); } } return zs; } /** * Get the specified property. *

* Use this method to retrieve the calculated statistics as a map of {@code ZonalStats} per band * by setting {@code name} to {@linkplain ZonalStatsDescriptor#ZONAL_STATS_PROPERTY}. * * @param name property name * * @return the requested property */ @Override public Object getProperty( String name ) { if (ZonalStatsDescriptor.ZONAL_STATS_PROPERTY.equalsIgnoreCase(name)) { return compileStatistics(); } else { return super.getProperty(name); } } /** * Get the class of the given property. For * {@linkplain ZonalStatsDescriptor#ZONAL_STATS_PROPERTY} this will return * {@code Map.class}. * * @param name property name * * @return the property class */ @Override public Class getPropertyClass( String name ) { if (ZonalStatsDescriptor.ZONAL_STATS_PROPERTY.equalsIgnoreCase(name)) { return Map.class; } else { return super.getPropertyClass(name); } } /** * Get all property names * @return property names as an array of Strings */ @Override public String[] getPropertyNames() { String[] names; int k = 0; String[] superNames = super.getPropertyNames(); if (superNames != null) { names = new String[superNames.length + 1]; for( String name : super.getPropertyNames() ) { names[k++] = name; } } else { names = new String[1]; } names[k] = ZonalStatsDescriptor.ZONAL_STATS_PROPERTY; return names; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy