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 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 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. + * + *

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}.

+ */ + 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. - *
  • Otherwise this method delegates to {@link #computeSequentially(Rectangle)}.
  • + *
  • Otherwise this method delegates to {@link #computeSequentially()}.
  • * * - * 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 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}).

    * - * @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; * * * + *

    Area of interest

    + * Some operations accept an optional area of interest 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}. + * *

    Error handling

    * 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 { * {@code "statistics"} * Statistics or image from which to get statistics. * {@link Statistics} or {@link RenderedImage} + * + * {@code "areaOfInterest"} + * Pixel coordinates of the region for which to compute statistics. + * {@link Shape} * * * 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 { * *

    Values should be instances of {@linkplain org.apache.sis.math.Statistics}[]. * 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.

    * - * @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. * + *

    This method may be invoked concurrently by many threads. + * Fields used by this method shall be thread-safe when not modified.

    + * * @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 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 0); }