sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/03: ImageProcessor.statistics(…) method needs to check if cached result is compatible with requested area of interest.
Date Thu, 25 Jun 2020 14:23:23 GMT
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 61f3899f35d965a7a200b0827fc9b5ce307a19b1
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Thu Jun 25 15:22:58 2020 +0200

    ImageProcessor.statistics(…) method needs to check if cached result is compatible with
requested area of interest.
---
 .../java/org/apache/sis/image/AnnotatedImage.java  | 66 ++++++++++++++++++----
 .../java/org/apache/sis/image/ImageProcessor.java  | 38 ++++++++++---
 .../java/org/apache/sis/image/PlanarImage.java     |  2 +
 3 files changed, 86 insertions(+), 20 deletions(-)

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 e062d62..82222ef 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
@@ -149,6 +149,9 @@ abstract class AnnotatedImage extends ImageAdapter {
      * <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>
+     *
+     * <p>If {@code areaOfInterest} is {@code null}, then {@link #boundsOfInterest}
is always {@code null}.
+     * However the converse is not necessarily true.</p>
      */
     protected final Shape areaOfInterest;
 
@@ -157,6 +160,9 @@ abstract class AnnotatedImage extends ImageAdapter {
      * 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.
+     *
+     * <p>If {@link #areaOfInterest} is {@code null}, then {@code boundsOfInterest}
is always {@code null}.
+     * However the converse is not necessarily true.</p>
      */
     protected final Rectangle boundsOfInterest;
 
@@ -190,18 +196,31 @@ abstract class AnnotatedImage extends ImageAdapter {
                              final boolean parallel, final boolean failOnException)
     {
         super(source);
+        Rectangle bounds = null;
         if (areaOfInterest != null) {
-            boundsOfInterest = areaOfInterest.getBounds();
-            ImageUtilities.clipBounds(source, boundsOfInterest);
-            if (areaOfInterest.contains(boundsOfInterest)) {
-                areaOfInterest = boundsOfInterest;
+            bounds = areaOfInterest.getBounds();
+            ImageUtilities.clipBounds(source, bounds);
+            if (areaOfInterest.contains(bounds)) {
+                areaOfInterest = bounds;
+            }
+            /*
+             * If the rectangle contains the full image, replace them by a null value.
+             * It allows optimizations (avoid the need to check for point inclusion)
+             * and allows the cache to detect that a value already exist.
+             */
+            if (bounds.x == getMinX() && bounds.width  == getWidth() &&
+                bounds.y == getMinY() && bounds.height == getHeight())
+            {
+                if (bounds == areaOfInterest) {
+                    areaOfInterest = null;
+                }
+                bounds = null;
             }
-        } else {
-            boundsOfInterest = null;
         }
-        this.areaOfInterest  = areaOfInterest;
-        this.parallel        = parallel;
-        this.failOnException = failOnException;
+        this.boundsOfInterest = bounds;
+        this.areaOfInterest   = areaOfInterest;
+        this.parallel         = parallel;
+        this.failOnException  = failOnException;
         /*
          * The `this.source` field should be as specified, even if it is another `AnnotatedImage`,
          * for allowing computation of properties managed by those other instances. However
we try
@@ -222,6 +241,20 @@ abstract class AnnotatedImage extends ImageAdapter {
     }
 
     /**
+     * If the source image is the same operation for the same area of interest, returns that
source.
+     * Otherwise returns {@code this} or a previous instance doing the same operation than
{@code this}.
+     *
+     * @see #equals(Object)
+     */
+    final RenderedImage unique() {
+        if (source.getClass() == getClass() && equalParameters((AnnotatedImage) source))
{
+            return source;
+        } else {
+            return ImageProcessor.unique(this);
+        }
+    }
+
+    /**
      * Returns the key to use for entries in the {@link #cache} map.
      *
      * @param  property  value of {@link #getPropertyNames()}.
@@ -492,7 +525,7 @@ abstract class AnnotatedImage extends ImageAdapter {
      */
     @Override
     public int hashCode() {
-        return super.hashCode() + Objects.hashCode(areaOfInterest);
+        return super.hashCode() + Objects.hashCode(areaOfInterest) + Boolean.hashCode(failOnException);
     }
 
     /**
@@ -504,7 +537,16 @@ abstract class AnnotatedImage extends ImageAdapter {
      */
     @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`.
+        return super.equals(object) && equalParameters((AnnotatedImage) object);
+    }
+
+    /**
+     * Returns {@code true} if the area of interest and some other fields are equal.
+     * The {@link #boundsOfInterest} is omitted because it is derived from {@link #areaOfInterest}.
+     * The {@link #errors} is omitted because it is part of computation results.
+     */
+    private boolean equalParameters(final AnnotatedImage other) {
+        return Objects.equals(areaOfInterest, other.areaOfInterest) &&
+                parallel == other.parallel && failOnException == other.failOnException;
     }
 }
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 05fa35e..913cd15 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
@@ -428,8 +428,16 @@ public class ImageProcessor implements Cloneable {
      * extracting immediately the statistics property value, except that errors are handled
by the
      * {@linkplain #getErrorAction() error handler}.
      *
+     * <p>If {@code areaOfInterest} is {@code null}, then the default is as below:</p>
+     * <ul>
+     *   <li>If the {@value StatisticsCalculator#STATISTICS_KEY} property value exists
in the given image,
+     *       then that value is returned. Note that they are not necessarily statistics for
the whole image.
+     *       They are whatever statistics the property provided considered as representative.</li>
+     *   <li>Otherwise statistics are computed for the whole image.</li>
+     * </ul>
+     *
      * @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.
+     * @param  areaOfInterest  pixel coordinates of the area of interest, or {@code null}
for the default.
      * @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}.
@@ -448,8 +456,12 @@ public class ImageProcessor implements Cloneable {
                 failOnException = failOnException();
                 errorListener   = errorListener();
             }
-            final StatisticsCalculator calculator = new StatisticsCalculator(
-                    source, areaOfInterest, parallel, failOnException);
+            /*
+             * No need to check if the given source is already an instance of StatisticsCalculator.
+             * The way AnnotatedImage cache mechanism is implemented, if statistics result
already
+             * exist, they will be used.
+             */
+            final AnnotatedImage calculator = new StatisticsCalculator(source, areaOfInterest,
parallel, failOnException);
             property = calculator.getProperty(StatisticsCalculator.STATISTICS_KEY);
             calculator.logAndClearError(ImageProcessor.class, "getStatistics", errorListener);
         }
@@ -458,12 +470,20 @@ public class ImageProcessor implements Cloneable {
 
     /**
      * Returns an image with statistics (minimum, maximum, mean, standard deviation) on each
bands.
-     * If the given image already contains an {@value StatisticsCalculator#STATISTICS_KEY}
property,
-     * 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).
      *
+     * <p>If {@code areaOfInterest} is {@code null}, then the default is as below:</p>
+     * <ul>
+     *   <li>If the {@value StatisticsCalculator#STATISTICS_KEY} property value exists
in the given image,
+     *       then that image is returned as-is. Note that the existing property value is
not necessarily
+     *       statistics for the whole image.
+     *       They are whatever statistics the property provided considered as representative.</li>
+     *   <li>Otherwise an image augmented with a {@value StatisticsCalculator#STATISTICS_KEY}
property value
+     *       is returned.</li>
+     * </ul>
+     *
      * @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.
+     * @param  areaOfInterest  pixel coordinates of the area of interest, or {@code null}
for the default.
      * @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.
      *
@@ -471,7 +491,9 @@ public class ImageProcessor implements Cloneable {
      * @see StatisticsCalculator#STATISTICS_KEY
      */
     public RenderedImage statistics(final RenderedImage source, final Shape areaOfInterest)
{
-        if (source == null || ArraysExt.contains(source.getPropertyNames(), StatisticsCalculator.STATISTICS_KEY))
{
+        if (source == null || (areaOfInterest == null &&
+                ArraysExt.contains(source.getPropertyNames(), StatisticsCalculator.STATISTICS_KEY)))
+        {
             return source;
         }
         final boolean parallel, failOnException;
@@ -479,7 +501,7 @@ public class ImageProcessor implements Cloneable {
             parallel        = parallel(source);
             failOnException = failOnException();
         }
-        return unique(new StatisticsCalculator(source, areaOfInterest, parallel, failOnException));
+        return new StatisticsCalculator(source, areaOfInterest, parallel, failOnException).unique();
     }
 
     /**
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 126ed47..5ea8970 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
@@ -165,6 +165,8 @@ public abstract class PlanarImage implements RenderedImage {
      * may have to {@linkplain ImageProcessor#statistics(RenderedImage, Shape) compute statistics
itself}
      * (by iterating over pixel values) when needed.</p>
      *
+     * <p>Statistics are only indicative. They may be computed on an image sub-region.</p>
+     *
      * @see ImageProcessor#statistics(RenderedImage, Shape)
      */
     public static final String STATISTICS_KEY = "org.apache.sis.Statistics";


Mime
View raw message