This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 41e9f4410d0412001227bcbaf1b8700d9f8bd21f
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Jun 15 16:14:18 2020 +0200
Specify an area of interest when computing statistics.
---
.../apache/sis/gui/coverage/CoverageCanvas.java | 2 +-
.../org/apache/sis/gui/coverage/RenderingData.java | 20 ++++-
.../java/org/apache/sis/image/AnnotatedImage.java | 85 +++++++++++++++++-----
.../java/org/apache/sis/image/DefaultIterator.java | 14 ++++
.../java/org/apache/sis/image/ImageProcessor.java | 37 +++++++---
.../java/org/apache/sis/image/PixelIterator.java | 13 ++++
.../java/org/apache/sis/image/PlanarImage.java | 4 +-
.../java/org/apache/sis/image/RecoloredImage.java | 5 +-
.../org/apache/sis/image/StatisticsCalculator.java | 49 ++++++-------
.../apache/sis/image/StatisticsCalculatorTest.java | 19 ++++-
10 files changed, 181 insertions(+), 67 deletions(-)
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
index 95f1d30..1ddcbe2 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -414,7 +414,7 @@ public class CoverageCanvas extends MapCanvasAWT {
resampledToDisplay = data.getTransform(objectiveToDisplay);
}
if (filteredImage == null) {
- filteredImage = data.filter(resampledImage);
+ filteredImage = data.filter(resampledImage, displayBounds);
}
prefetchedImage = data.prefetch(filteredImage, resampledToDisplay, displayBounds);
}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
index 42a3e72..c269bb2 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java
@@ -24,6 +24,7 @@ import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Rectangle2D;
import org.opengis.util.FactoryException;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.CoordinateOperation;
@@ -268,9 +269,10 @@ final class RenderingData implements Cloneable {
* Applies the image operation (if any) on the given resampled image, than stretches
the color ramp.
*
* @param resampledImage the image computed by {@link #resample(CoordinateReferenceSystem,
LinearTransform)}.
+ * @param displayBounds size and location of the display device, in pixel units.
* @return image with operation applied and color ramp stretched. May be the same instance
than given image.
*/
- final RenderedImage filter(RenderedImage resampledImage) {
+ final RenderedImage filter(RenderedImage resampledImage, final Rectangle2D displayBounds)
{
/*
* If the operation is not `NONE` but following call to `apply(…)` returns `resampledImage`
unchanged,
* it means that the operation can not be applied on that image. We should reset
operation to `NONE`,
@@ -289,9 +291,21 @@ final class RenderingData implements Cloneable {
*/
if (selectedDerivative.operation == ImageOperation.NONE) {
if (statistics == null) {
- statistics = processor.getStatistics(data);
+ statistics = processor.getStatistics(data, null);
}
modifiers.put("statistics", statistics);
+ } else {
+ /*
+ * If an operation is applied, compute statistics only for currently visible
region.
+ * This is necessary because zoomed images may be very large. This is usually
not a
+ * problem because only requested tiles are computed, but statistics requested
without
+ * bounds would cause all those tiles to be computed.
+ *
+ * Inconvenient is that visual appareance is not stable: the color ramp may
change
+ * after every zoom, or may not fit data anymore after a pan. Since we need
to revisit
+ * the coverage operation framework anyway, we live with that problem for
now.
+ */
+ modifiers.put("areaOfInterest", displayBounds.getBounds());
}
if (selectedDerivative.styling == Stretching.AUTOMATIC) {
modifiers.put("MultStdDev", 3);
@@ -305,7 +319,7 @@ final class RenderingData implements Cloneable {
* Computes immediately, possibly using many threads, the tiles that are going to be
displayed.
* The returned instance should be used only for current rendering event; it should not
be cached.
*
- * @param filteredImage the image computed by {@link #filter(RenderedImage)}.
+ * @param filteredImage the image computed by {@link #filter(RenderedImage, Rectangle2D)}.
* @param resampledToDisplay the transform computed by {@link #getTransform(LinearTransform)}.
* @param displayBounds size and location of the display device, in pixel units.
* @return a temporary image with tiles intersecting the display region already computed.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java
index 57b707c..0b868cd 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java
@@ -17,12 +17,14 @@
package org.apache.sis.image;
import java.util.Locale;
+import java.util.Objects;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Filter;
import java.util.stream.Collector;
import java.awt.Image;
+import java.awt.Shape;
import java.awt.Rectangle;
import java.awt.image.RenderedImage;
import java.awt.image.Raster;
@@ -33,6 +35,7 @@ import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.collection.Cache;
import org.apache.sis.internal.system.Modules;
import org.apache.sis.internal.coverage.j2d.TileOpExecutor;
+import org.apache.sis.internal.coverage.j2d.ImageUtilities;
/**
@@ -99,6 +102,25 @@ abstract class AnnotatedImage extends ImageAdapter {
private final Cache<String,Object> cache;
/**
+ * Pixel coordinates of the region for which to compute the values, or {@code null} for
the whole image.
+ * If non-null, the {@link Shape#contains(double, double)} method may be invoked for
testing if a pixel
+ * shall be included in the computation or not.
+ *
+ * <p>This shape should not be modified, either by this class or by the caller
who provided the shape.
+ * The {@code Shape} implementation shall be thread-safe, assuming its state stay unmodified,
unless
+ * the {@link #parallel} argument specified to the constructor was {@code false}.</p>
+ */
+ protected final Shape areaOfInterest;
+
+ /**
+ * Bounds of {@link #areaOfInterest} intersected with image bounds, or {@code null} for
the whole image.
+ * If the area of interest fully contains those bounds, then {@link #areaOfInterest}
is set to the same
+ * reference than {@code boundsOfInterest}. Subclasses can use {@code areaOfInterest
== boundsOfInterest}
+ * for quickly testing if the area of interest is rectangular.
+ */
+ protected final Rectangle boundsOfInterest;
+
+ /**
* The errors that occurred while computing the result, or {@code null} if none or not
yet determined.
* This field is never set if {@link #failOnException} is {@code true}.
*/
@@ -120,11 +142,24 @@ abstract class AnnotatedImage extends ImageAdapter {
* The annotations are the additional properties computed by the subclass.
*
* @param source the image to wrap for adding properties (annotations).
+ * @param areaOfInterest pixel coordinates of AOI, or {@code null} for the whole image.
* @param parallel whether parallel execution is authorized.
* @param failOnException whether errors occurring during computation should be propagated.
*/
- protected AnnotatedImage(RenderedImage source, final boolean parallel, final boolean
failOnException) {
+ protected AnnotatedImage(RenderedImage source, Shape areaOfInterest,
+ final boolean parallel, final boolean failOnException)
+ {
super(source);
+ if (areaOfInterest != null) {
+ boundsOfInterest = areaOfInterest.getBounds();
+ ImageUtilities.clipBounds(source, boundsOfInterest);
+ if (areaOfInterest.contains(boundsOfInterest)) {
+ areaOfInterest = boundsOfInterest;
+ }
+ } else {
+ boundsOfInterest = null;
+ }
+ this.areaOfInterest = areaOfInterest;
this.parallel = parallel;
this.failOnException = failOnException;
/*
@@ -188,8 +223,7 @@ abstract class AnnotatedImage extends ImageAdapter {
/**
* Gets a property from this image or from its source. If the given name is for the property
* to be computed by this class and if that property has not been computed before, then
this
- * method invokes {@link #computeProperty(Rectangle)} with a {@code null} "area of interest"
- * argument value. That {@code computeProperty(…)} result will be cached.
+ * method invokes {@link #computeProperty()} and caches its result.
*
* @param name name of the property to get.
* @return the property for the given name ({@code null} is a valid result),
@@ -212,7 +246,7 @@ abstract class AnnotatedImage extends ImageAdapter {
try {
value = handler.peek();
if (value == null) {
- value = computeProperty(null);
+ value = computeProperty();
if (value == null) value = NULL;
success = (errors == null);
}
@@ -305,21 +339,15 @@ abstract class AnnotatedImage extends ImageAdapter {
* and the area of interest covers at least two tiles, then this method distributes
* calculation on many threads using the functions provided by the collector.
* See {@link #collector()} Javadoc for more information.</li>
- * <li>Otherwise this method delegates to {@link #computeSequentially(Rectangle)}.</li>
+ * <li>Otherwise this method delegates to {@link #computeSequentially()}.</li>
* </ul>
*
- * The {@code areaOfInterest} argument is {@code null} by default, which means to calculate
- * the property on all tiles. This argument exists for allowing subclasses to override
this
- * method and invoke {@code super.computeProperty(…)} with a sub-region to compute.
- *
- * @param areaOfInterest pixel coordinates of the region of interest, or {@code null}
for the whole image.
- * It is caller responsibility to ensure that this rectangle is fully included
inside image bounds.
* @return the computed property value. Note that {@code null} is a valid result.
* @throws Exception if an error occurred while computing the property.
*/
- protected Object computeProperty(final Rectangle areaOfInterest) throws Exception {
+ protected Object computeProperty() throws Exception {
if (parallel) {
- final TileOpExecutor executor = new TileOpExecutor(source, areaOfInterest);
+ final TileOpExecutor executor = new TileOpExecutor(source, boundsOfInterest);
if (executor.isMultiTiled()) {
final Collector<? super Raster,?,?> collector = collector();
if (collector != null) {
@@ -327,7 +355,7 @@ abstract class AnnotatedImage extends ImageAdapter {
}
}
}
- return computeSequentially(areaOfInterest);
+ return computeSequentially();
}
/**
@@ -339,13 +367,10 @@ abstract class AnnotatedImage extends ImageAdapter {
* returned {@code null}), or when it is not worth to parallelize (image has only one
tile), or when
* the {@linkplain #source} image may be non-thread safe ({@link #parallel} is {@code
false}).</p>
*
- * @param areaOfInterest pixel coordinates of the region of interest, or {@code null}
for the whole image.
- * This is the argument given to {@link #computeProperty(Rectangle)} and can
usually be ignored
- * (because always {@code null}) if that method has not been overridden.
* @return the computed property value. Note that {@code null} is a valid result.
* @throws Exception if an error occurred while computing the property.
*/
- protected abstract Object computeSequentially(Rectangle areaOfInterest) throws Exception;
+ protected abstract Object computeSequentially() throws Exception;
/**
* Returns the function to execute for computing the property value, together with other
required functions
@@ -408,4 +433,28 @@ abstract class AnnotatedImage extends ImageAdapter {
buffer.append('"').append(key).append('"');
return AnnotatedImage.class;
}
+
+ /**
+ * Returns a hash code value for this image. This method should be quick;
+ * it should not compute the hash code from sample values.
+ *
+ * @return a hash code value based on a description of the operation performed by this
image.
+ */
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hashCode(areaOfInterest);
+ }
+
+ /**
+ * Compares the given object with this image for equality. This method should be quick
and compare
+ * how images compute their values from their sources; it should not compare the actual
pixel values.
+ *
+ * @param object the object to compare with this image.
+ * @return {@code true} if the given object is an image performing the same calculation
than this image.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ return super.equals(object) && Objects.equals(areaOfInterest, ((AnnotatedImage)
object).areaOfInterest);
+ // The `boundsOfInterest` is omitted because it is derived from `areaOfInterest`.
+ }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/DefaultIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/DefaultIterator.java
index e5daa80..35f0506 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/DefaultIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/DefaultIterator.java
@@ -20,6 +20,7 @@ import java.util.Optional;
import java.awt.Point;
import java.awt.Dimension;
import java.awt.Rectangle;
+import java.awt.Shape;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
@@ -180,6 +181,19 @@ class DefaultIterator extends WritablePixelIterator {
}
/**
+ * Returns {@code true} if current iterator position is inside the given shape.
+ * Current version does not verify if iteration started or finished
+ * (this method is non-public for that reason).
+ *
+ * @param domain the shape for which to test inclusion.
+ * @return whether current iterator position is inside the given shape.
+ */
+ @Override
+ boolean isInside(final Shape domain) {
+ return domain.contains(x, y);
+ }
+
+ /**
* Moves the pixel iterator to the given column (x) and row (y) indices.
*
* @param px the column index of the pixel to make current.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
index f339abf..82a80e6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageProcessor.java
@@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.Objects;
import java.util.logging.Filter;
import java.util.logging.LogRecord;
+import java.awt.Shape;
import java.awt.Rectangle;
import java.awt.image.ColorModel;
import java.awt.image.SampleModel;
@@ -65,6 +66,13 @@ import org.apache.sis.measure.Units;
* </li>
* </ul>
*
+ * <h2>Area of interest</h2>
+ * Some operations accept an optional <cite>area of interest</cite> argument
specified as a {@link Shape} instance in
+ * pixel coordinates. If a shape is given, it should not be modified after {@code ImageProcessor}
method call because
+ * the given object may be retained directly (i.e. the {@code Shape} is not always cloned;
it depends on its class).
+ * In addition, the {@code Shape} implementation shall be thread-safe (assuming its state
stay unmodified)
+ * unless the execution mode is set to {@link Mode#PARALLEL}.
+ *
* <h2>Error handling</h2>
* If an exception occurs during the computation of a tile, then the {@code ImageProcessor}
behavior
* is controlled by the {@link #getErrorAction() errorAction} property:
@@ -428,23 +436,25 @@ public class ImageProcessor implements Cloneable {
/**
* Returns statistics (minimum, maximum, mean, standard deviation) on each bands of the
given image.
- * Invoking this method is equivalent to invoking {@link #statistics(RenderedImage)}
and extracting
- * immediately the statistics property value, except that errors are handled by the
+ * Invoking this method is equivalent to invoking {@link #statistics(RenderedImage, Shape)}
and
+ * extracting immediately the statistics property value, except that errors are handled
by the
* {@linkplain #getErrorAction() error handler}.
*
- * @param source the image for which to compute statistics.
+ * @param source the image for which to compute statistics.
+ * @param areaOfInterest pixel coordinates of the area of interest, or {@code null}
for the whole image.
* @return the statistics of sample values in each band.
* @throws ImagingOpException if an error occurred during calculation
* and the error handler is {@link ErrorAction#THROW}.
*
- * @see #statistics(RenderedImage)
+ * @see #statistics(RenderedImage, Shape)
* @see StatisticsCalculator#STATISTICS_KEY
*/
- public Statistics[] getStatistics(final RenderedImage source) {
+ public Statistics[] getStatistics(final RenderedImage source, final Shape areaOfInterest)
{
ArgumentChecks.ensureNonNull("source", source);
Object property = source.getProperty(StatisticsCalculator.STATISTICS_KEY);
if (!(property instanceof Statistics[])) {
- final StatisticsCalculator calculator = new StatisticsCalculator(source, parallel(source),
failOnException());
+ final StatisticsCalculator calculator = new StatisticsCalculator(
+ source, areaOfInterest, parallel(source), failOnException());
property = calculator.getProperty(StatisticsCalculator.STATISTICS_KEY);
calculator.logAndClearError(ImageProcessor.class, "getStatistics", errorListener());
}
@@ -457,16 +467,17 @@ public class ImageProcessor implements Cloneable {
* then that image is returned as-is. Otherwise this method returns a new image having
that property.
* The property value will be computed when first requested (it is not computed by this
method).
*
- * @param source the image for which to provide statistics (may be {@code null}).
+ * @param source the image for which to provide statistics (may be {@code null}).
+ * @param areaOfInterest pixel coordinates of the area of interest, or {@code null}
for the whole image.
* @return an image with an {@value StatisticsCalculator#STATISTICS_KEY} property.
* May be {@code image} if the given argument is null or already has a statistics
property.
*
- * @see #getStatistics(RenderedImage)
+ * @see #getStatistics(RenderedImage, Shape)
* @see StatisticsCalculator#STATISTICS_KEY
*/
- public RenderedImage statistics(final RenderedImage source) {
+ public RenderedImage statistics(final RenderedImage source, final Shape areaOfInterest)
{
return (source == null) || ArraysExt.contains(source.getPropertyNames(), StatisticsCalculator.STATISTICS_KEY)
- ? source : unique(new StatisticsCalculator(source, parallel(source), failOnException()));
+ ? source : unique(new StatisticsCalculator(source, areaOfInterest, parallel(source),
failOnException()));
}
/**
@@ -496,7 +507,7 @@ public class ImageProcessor implements Cloneable {
* Returns an image with the same sample values than the given image, but with its color
ramp stretched between
* automatically determined bounds. This is the same operation than {@link #stretchColorRamp(RenderedImage,
* double, double) stretchColorRamp(…)} except that the minimum and maximum values
are determined by
- * {@linkplain #getStatistics(RenderedImage) statistics} on the image:
+ * {@linkplain #getStatistics(RenderedImage, Shape) statistics} on the image:
* a range of value is determined first from the {@linkplain Statistics#minimum() minimum}
and
* {@linkplain Statistics#maximum() maximum} values found in the image, optionally narrowed
to an interval
* of some {@linkplain Statistics#standardDeviation(boolean) standard deviations} around
the mean value.
@@ -526,6 +537,10 @@ public class ImageProcessor implements Cloneable {
* <td>{@code "statistics"}</td>
* <td>Statistics or image from which to get statistics.</td>
* <td>{@link Statistics} or {@link RenderedImage}</td>
+ * </tr><tr>
+ * <td>{@code "areaOfInterest"}</td>
+ * <td>Pixel coordinates of the region for which to compute statistics.</td>
+ * <td>{@link Shape}</td>
* </tr>
* </table>
*
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
index f3e53fd..6cdcca8 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
@@ -22,6 +22,7 @@ import java.nio.Buffer;
import java.awt.Point;
import java.awt.Dimension;
import java.awt.Rectangle;
+import java.awt.Shape;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.BufferedImage;
@@ -546,6 +547,18 @@ public abstract class PixelIterator {
public abstract Point getPosition();
/**
+ * Returns {@code true} if current iterator position is inside the given shape.
+ * Current version does not require implementations to check if iteration started or
finished
+ * (this method is non-public for that reason).
+ *
+ * @param domain the shape for which to test inclusion.
+ * @return whether current iterator position is inside the given shape.
+ */
+ boolean isInside(final Shape domain) {
+ return domain.contains(getPosition());
+ }
+
+ /**
* Moves the pixel iterator to the given column (x) and row (y) indices. After this method
invocation,
* the iterator state is as if the {@link #next()} method has been invoked just before
to reach the
* specified position.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
index 4344a71..32547ba 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
@@ -145,10 +145,10 @@ public abstract class PlanarImage implements RenderedImage {
*
* <p>Values should be instances of <code>{@linkplain org.apache.sis.math.Statistics}[]</code>.
* The array length should be the number of bands. If this property is not provided,
Apache SIS
- * may have to {@linkplain ImageProcessor#statistics(RenderedImage) compute statistics
itself}
+ * may have to {@linkplain ImageProcessor#statistics(RenderedImage, Shape) compute statistics
itself}
* (by iterating over pixel values) when needed.</p>
*
- * @see ImageProcessor#statistics(RenderedImage)
+ * @see ImageProcessor#statistics(RenderedImage, Shape)
*/
public static final String STATISTICS_KEY = "org.apache.sis.Statistics";
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/RecoloredImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/RecoloredImage.java
index c70970e..fbc9993 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/RecoloredImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/RecoloredImage.java
@@ -17,6 +17,7 @@
package org.apache.sis.image;
import java.util.Map;
+import java.awt.Shape;
import java.awt.image.ColorModel;
import java.awt.image.SampleModel;
import java.awt.image.RenderedImage;
@@ -134,7 +135,9 @@ final class RecoloredImage extends ImageAdapter {
if (visibleBand >= 0) {
if (statistics == null) {
if (statsAllBands == null) {
- statsAllBands = processor.getStatistics(statsSource);
+ final Object areaOfInterest = modifiers.get("areaOfInterest");
+ statsAllBands = processor.getStatistics(statsSource,
+ (areaOfInterest instanceof Shape) ? (Shape) areaOfInterest :
null);
}
if (statsAllBands != null && visibleBand < statsAllBands.length)
{
statistics = statsAllBands[visibleBand];
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/StatisticsCalculator.java
b/core/sis-feature/src/main/java/org/apache/sis/image/StatisticsCalculator.java
index e413429..b0af678 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/StatisticsCalculator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/StatisticsCalculator.java
@@ -16,7 +16,7 @@
*/
package org.apache.sis.image;
-import java.awt.Rectangle;
+import java.awt.Shape;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.ImagingOpException;
@@ -40,13 +40,14 @@ final class StatisticsCalculator extends AnnotatedImage {
* Creates a new calculator.
*
* @param image the image for which to compute statistics.
+ * @param areaOfInterest pixel coordinates of AOI, or {@code null} for the whole image.
* @param parallel whether parallel execution is authorized.
* @param failOnException whether errors occurring during computation should be propagated.
*/
- StatisticsCalculator(final RenderedImage image,
+ StatisticsCalculator(final RenderedImage image, final Shape areaOfInterest,
final boolean parallel, final boolean failOnException)
{
- super(image, parallel, failOnException);
+ super(image, areaOfInterest, parallel, failOnException);
}
/**
@@ -75,44 +76,38 @@ final class StatisticsCalculator extends AnnotatedImage {
* This method is invoked in both sequential and parallel case. In the sequential case
it
* is invoked for the whole image; in the parallel case it is invoked for only one tile.
*
+ * <p>This method may be invoked concurrently by many threads.
+ * Fields used by this method shall be thread-safe when not modified.</p>
+ *
* @param accumulator where to accumulate the statistics results.
* @param it the iterator on a raster or on the whole image.
*/
- private static void compute(final Statistics[] accumulator, final PixelIterator it) {
+ private void compute(final Statistics[] accumulator, final PixelIterator it) {
double[] samples = null;
while (it.next()) {
- samples = it.getPixel(samples); // Get values in all bands.
- for (int i=0; i<accumulator.length; i++) {
- accumulator[i].accept(samples[i]);
+ if (areaOfInterest == null || it.isInside(areaOfInterest)) {
+ samples = it.getPixel(samples); // Get values in all bands.
+ for (int i=0; i<accumulator.length; i++) {
+ accumulator[i].accept(samples[i]);
+ }
}
}
}
/**
- * Computes statistics on the given image in a sequential way (everything computed in
current thread).
- * This is used for testing purposes, or when the image has only one tile, or when the
implementation
- * of {@link RenderedImage#getTile(int, int)} may be non thread-safe.
- *
- * @param source the image on which to compute statistics.
- * @return statistics on the given image computed sequentially.
+ * Computes the statistics on the image using a single thread. This is used for testing
purposes, or when
+ * the image has only one tile, or when the implementation of {@link RenderedImage#getTile(int,
int)} may
+ * be non thread-safe.
*/
- static Statistics[] computeSequentially(final RenderedImage source) {
- final PixelIterator it = PixelIterator.create(source);
+ @Override
+ protected Object computeSequentially() {
+ final PixelIterator it = new PixelIterator.Builder().setRegionOfInterest(boundsOfInterest).create(source);
final Statistics[] accumulator = createAccumulator(it.getNumBands());
compute(accumulator, it);
return accumulator;
}
/**
- * Computes the statistics on the whole image using a single thread. This method is invoked
when it is
- * not worth to parallelize (image has only one tile), or when the source image may be
non-thread safe.
- */
- @Override
- protected Object computeSequentially(Rectangle areaOfInterest) {
- return computeSequentially(source);
- }
-
- /**
* Invoked when a property of the given name has been requested and that property is
cached.
* The property should be cloned before to be returned to the user in order to protect
this image state.
*/
@@ -131,7 +126,7 @@ final class StatisticsCalculator extends AnnotatedImage {
*/
@Override
protected Collector<Raster, Statistics[], Statistics[]> collector() {
- return Collector.of(this::createAccumulator, StatisticsCalculator::compute, StatisticsCalculator::combine);
+ return Collector.of(this::createAccumulator, this::compute, StatisticsCalculator::combine);
}
/**
@@ -170,7 +165,7 @@ final class StatisticsCalculator extends AnnotatedImage {
* @param tile the tile on which to perform a computation.
* @throws RuntimeException if the calculation failed.
*/
- private static void compute(final Statistics[] accumulator, final Raster tile) {
- compute(accumulator, new PixelIterator.Builder().create(tile));
+ private void compute(final Statistics[] accumulator, final Raster tile) {
+ compute(accumulator, new PixelIterator.Builder().setRegionOfInterest(boundsOfInterest).create(tile));
}
}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/StatisticsCalculatorTest.java
b/core/sis-feature/src/test/java/org/apache/sis/image/StatisticsCalculatorTest.java
index 48cd363..02d206a 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/StatisticsCalculatorTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/StatisticsCalculatorTest.java
@@ -18,6 +18,7 @@ package org.apache.sis.image;
import java.util.Random;
import java.awt.image.DataBuffer;
+import java.awt.image.RenderedImage;
import java.awt.image.ImagingOpException;
import org.apache.sis.internal.system.Modules;
import org.apache.sis.math.Statistics;
@@ -76,6 +77,16 @@ public final strictfp class StatisticsCalculatorTest extends TestCase {
}
/**
+ * Computes statistics on the given image in a sequential way (everything computed in
current thread).
+ *
+ * @param source the image on which to compute statistics.
+ * @return statistics on the given image computed sequentially.
+ */
+ private static Statistics[] computeSequentially(final RenderedImage source) {
+ return (Statistics[]) new StatisticsCalculator(source, null, false, true).computeSequentially();
+ }
+
+ /**
* Tests with parallel execution. The result of sequential execution is used as a reference.
*/
@Test
@@ -83,8 +94,8 @@ public final strictfp class StatisticsCalculatorTest extends TestCase {
final ImageProcessor operations = new ImageProcessor();
operations.setExecutionMode(ImageProcessor.Mode.PARALLEL);
final TiledImageMock image = createImage();
- final Statistics[] expected = StatisticsCalculator.computeSequentially(image);
- final Statistics[] actual = operations.getStatistics(image);
+ final Statistics[] expected = computeSequentially(image);
+ final Statistics[] actual = operations.getStatistics(image, null);
for (int i=0; i<expected.length; i++) {
final Statistics e = expected[i];
final Statistics a = actual [i];
@@ -105,7 +116,7 @@ public final strictfp class StatisticsCalculatorTest extends TestCase
{
final TiledImageMock image = createImage();
image.failRandomly(new Random(-8739538736973900203L));
try {
- operations.getStatistics(image);
+ operations.getStatistics(image, null);
fail("Expected ImagingOpException.");
} catch (ImagingOpException e) {
final String message = e.getMessage();
@@ -125,7 +136,7 @@ public final strictfp class StatisticsCalculatorTest extends TestCase
{
operations.setErrorAction(ImageProcessor.ErrorAction.LOG);
final TiledImageMock image = createImage();
image.failRandomly(new Random(8004277484984714811L));
- final Statistics[] stats = operations.getStatistics(image);
+ final Statistics[] stats = operations.getStatistics(image, null);
for (final Statistics a : stats) {
assertTrue(a.count() > 0);
}
|