sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Make processor configuration cloneable and use them in `ResampledGridCoverage`.
Date Wed, 01 Apr 2020 21:32:30 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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new bfe5583  Make processor configuration cloneable and use them in `ResampledGridCoverage`.
bfe5583 is described below

commit bfe55833ff7f9641fec438fe4c02f54626933496
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Apr 1 19:34:15 2020 +0200

    Make processor configuration cloneable and use them in `ResampledGridCoverage`.
---
 .../sis/coverage/grid/GridCoverageProcessor.java   | 82 +++++++++++++++++++---
 .../sis/coverage/grid/ResampledGridCoverage.java   | 52 +++++++-------
 .../java/org/apache/sis/image/ImageProcessor.java  | 51 +++++++++++++-
 .../coverage/grid/ResampledGridCoverageTest.java   | 37 +++++-----
 .../org/apache/sis/image/ResampledImageTest.java   |  8 ++-
 5 files changed, 176 insertions(+), 54 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
index abc1f15..6423318 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
@@ -16,11 +16,14 @@
  */
 package org.apache.sis.coverage.grid;
 
+import java.util.Objects;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.image.ImageProcessor;
 import org.apache.sis.image.Interpolation;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.collection.WeakHashSet;
 import org.apache.sis.internal.system.Modules;
 
 
@@ -38,26 +41,50 @@ import org.apache.sis.internal.system.Modules;
  * @since 1.1
  * @module
  */
-public class GridCoverageProcessor {
+public class GridCoverageProcessor implements Cloneable {
     /**
-     * The interpolation method to use for resampling operations.
+     * Configured {@link ImageProcessor} instances used by {@link GridCoverage}s created
by processors.
+     * We use this set for sharing common instances in {@link GridCoverage} instances, which
is okay
+     * provided that we do not modify the {@link ImageProcessor} configuration.
      */
-    private Interpolation interpolation;
+    private static final WeakHashSet<ImageProcessor> PROCESSORS = new WeakHashSet<>(ImageProcessor.class);
+
+    /**
+     * Returns an unique instance of the given processor. Both the given and the returned
processors
+     * shall be unmodified, because they may be shared by many {@link GridCoverage} instances.
+     */
+    static ImageProcessor unique(final ImageProcessor image) {
+        return PROCESSORS.unique(image);
+    }
+
+    /**
+     * The image processor to use for resampling operations.
+     */
+    private final ImageProcessor imageProcessor;
 
     /**
      * Creates a new set of grid coverage operations with default configuration.
      */
     public GridCoverageProcessor() {
-        interpolation = Interpolation.BILINEAR;
+        imageProcessor = new ImageProcessor();
     }
 
     /**
+     * Creates a new set of grid coverage operations with the given configuration.
+     *
+     * @param  processor  the processor to use for image operations.
+     */
+    public GridCoverageProcessor(final ImageProcessor processor) {
+        ArgumentChecks.ensureNonNull("processor", processor);
+        imageProcessor = processor;
+    }
+    /**
      * Returns the interpolation method to use for resampling operations.
      *
      * @return interpolation method to use in resampling operations.
      */
     public Interpolation getInterpolation() {
-        return interpolation;
+        return imageProcessor.getInterpolation();
     }
 
     /**
@@ -66,8 +93,7 @@ public class GridCoverageProcessor {
      * @param  method  interpolation method to use in resampling operations.
      */
     public void setInterpolation(final Interpolation method) {
-        ArgumentChecks.ensureNonNull("method", method);
-        interpolation = method;
+        imageProcessor.setInterpolation(method);
     }
 
     /**
@@ -113,7 +139,7 @@ public class GridCoverageProcessor {
             source = ((ResampledGridCoverage) source).source;
         }
         try {
-            return ResampledGridCoverage.create(source, target, interpolation);
+            return ResampledGridCoverage.create(source, target, imageProcessor);
         } catch (IllegalGridGeometryException e) {
             final Throwable cause = e.getCause();
             if (cause instanceof TransformException) {
@@ -135,4 +161,44 @@ public class GridCoverageProcessor {
     static void recoverableException(final String caller, final Exception ex) {
         Logging.recoverableException(Logging.getLogger(Modules.RASTER), GridCoverageProcessor.class,
caller, ex);
     }
+
+    /**
+     * Returns {@code true} if the given object is a coverage processor
+     * of the same class with the same configuration.
+     *
+     * @param  object  the other object to compare with this processor.
+     * @return whether the other object is a coverage processor of the same class with the
same configuration.
+     */
+    @Override
+    public boolean equals(final Object object) {
+        if (object != null && object.getClass() == getClass()) {
+            final GridCoverageProcessor other = (GridCoverageProcessor) object;
+            return imageProcessor.equals(other.imageProcessor);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash code value for this coverage processor based on its current configuration.
+     *
+     * @return a hash code value for this processor.
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(getClass(), imageProcessor);
+    }
+
+    /**
+     * Returns a coverage processor with the same configuration than this processor.
+     *
+     * @return a clone of this image processor.
+     */
+    @Override
+    public GridCoverageProcessor clone() {
+        try {
+            return (GridCoverageProcessor) super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError(e);
+        }
+    }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
index 8639e1c..d8b149a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
@@ -29,12 +29,10 @@ import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.MathTransform;
 import org.apache.sis.geometry.Envelopes;
-import org.apache.sis.image.Interpolation;
 import org.apache.sis.image.ImageProcessor;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.internal.util.DoubleDouble;
-import org.apache.sis.internal.coverage.j2d.ImageUtilities;
 import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
@@ -75,9 +73,10 @@ final class ResampledGridCoverage extends GridCoverage {
     private final MathTransform toSourceCorner, toSourceCenter;
 
     /**
-     * The interpolation method to use for resampling images.
+     * The image processor to use for resampling operations. Its configuration shall not
+     * be modified because this processor may be shared by different grid coverages.
      */
-    private final Interpolation interpolation;
+    private final ImageProcessor imageProcessor;
 
     /**
      * Indices of extent dimensions corresponding to image <var>x</var> and <var>y</var>
coordinates.
@@ -93,18 +92,17 @@ final class ResampledGridCoverage extends GridCoverage {
      * @param  domain          the grid extent, CRS and conversion from cell indices to CRS.
      * @param  toSourceCorner  transform from cell corner coordinates in this coverage to
source coverage.
      * @param  toSourceCenter  transform from cell center coordinates in this coverage to
source coverage.
-     * @param  interpolation   the interpolation method to use for resampling images.
+     * @param  processor       the image processor to use for resampling images.
      */
     private ResampledGridCoverage(final GridCoverage source, final GridGeometry domain,
                                   final MathTransform toSourceCorner,
                                   final MathTransform toSourceCenter,
-                                  final Interpolation interpolation)
+                                  ImageProcessor processor)
     {
         super(source, domain);
         this.source         = source;
         this.toSourceCorner = toSourceCorner;
         this.toSourceCenter = toSourceCenter;
-        this.interpolation  = interpolation;
         final GridExtent extent = domain.getExtent();
         long size1 = 0; int idx1 = 0;
         long size2 = 0; int idx2 = 1;
@@ -125,6 +123,23 @@ final class ResampledGridCoverage extends GridCoverage {
             xDimension = idx2;
             yDimension = idx1;
         }
+        /*
+         * Get fill values from background values declared for each band, if any.
+         * If no background value is declared, default is 0 for integer data or
+         * NaN for floating point values.
+         */
+        final List<SampleDimension> bands = getSampleDimensions();
+        final Number[] fillValues = new Number[bands.size()];
+        for (int i=fillValues.length; --i >= 0;) {
+            final SampleDimension band = bands.get(i);
+            final Optional<Number> bg = band.getBackground();
+            if (bg.isPresent()) {
+                fillValues[i] = bg.get();
+            }
+        }
+        processor = processor.clone();
+        processor.setFillValues(fillValues);
+        imageProcessor = GridCoverageProcessor.unique(processor);
     }
 
     /**
@@ -170,7 +185,7 @@ final class ResampledGridCoverage extends GridCoverage {
      * @throws IncompleteGridGeometryException if the source grid geometry is missing an
information.
      * @throws TransformException if some coordinates can not be transformed to the specified
target.
      */
-    static GridCoverage create(final GridCoverage source, final GridGeometry target, final
Interpolation interpolation)
+    static GridCoverage create(final GridCoverage source, final GridGeometry target, final
ImageProcessor processor)
             throws FactoryException, TransformException
     {
         final CoordinateReferenceSystem sourceCRS = source.getCoordinateReferenceSystem();
@@ -371,7 +386,7 @@ final class ResampledGridCoverage extends GridCoverage {
         return new ResampledGridCoverage(source, resampled,
                 MathTransforms.concatenate(targetCornerToCRS, sourceCornerToCRS.inverse()),
                 MathTransforms.concatenate(targetCenterToCRS, sourceCenterToCRS.inverse()),
-                interpolation).specialize(isGeometryExplicit);
+                processor).specialize(isGeometryExplicit);
     }
 
     /**
@@ -426,23 +441,6 @@ final class ResampledGridCoverage extends GridCoverage {
                 Math.subtractExact(image.getMinY(), sourceExtent.getLow(yDimension)));
 
         final MathTransform toImage = MathTransforms.concatenate(pixelsToTransform, toSourceCenter,
transformToPixels);
-        /*
-         * Get fill values from background values declared for each band, if any.
-         * If no background value is declared, default is 0 for integer data or
-         * NaN for floating point values.
-         */
-        final Number[] fillValues = new Number[ImageUtilities.getNumBands(image)];
-        final List<SampleDimension> bands = getSampleDimensions();
-        for (int i=Math.min(bands.size(), fillValues.length); --i >= 0;) {
-            final SampleDimension band = bands.get(i);
-            final Optional<Number> bg = band.getBackground();
-            if (bg.isPresent()) {
-                fillValues[i] = bg.get();
-            }
-        }
-        final ImageProcessor processor = new ImageProcessor();
-        processor.setInterpolation(interpolation);
-        processor.setFillValues(fillValues);
-        return processor.resample(bounds, toImage, image);
+        return imageProcessor.resample(bounds, toImage, image);
     }
 }
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 cf77c77..364a29a 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
@@ -17,6 +17,7 @@
 package org.apache.sis.image;
 
 import java.util.List;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.logging.Filter;
 import java.util.logging.LogRecord;
@@ -57,6 +58,9 @@ import org.apache.sis.referencing.operation.transform.MathTransforms;
  *     By default errors during calculation are propagated as an {@link ImagingOpException},
  *     in which case no result is available. But errors can also be notified as a {@link
LogRecord} instead,
  *     in which case partial results may be available.
+ *   </li><li>
+ *     Interpolation methods to use during resampling operations and fill values to use for
pixels that can not
+ *     be computed.
  *   </li>
  * </ul>
  *
@@ -84,7 +88,7 @@ import org.apache.sis.referencing.operation.transform.MathTransforms;
  * @since 1.1
  * @module
  */
-public class ImageProcessor {
+public class ImageProcessor implements Cloneable {
     /**
      * Cache of previously created images. We use this cache only for images of known implementations,
      * especially the ones which may be costly to compute. Reusing an existing instance avoid
to repeat
@@ -486,6 +490,8 @@ public class ImageProcessor {
         }
         if (cm != null && !cm.equals(resampled.getColorModel())) {
             resampled = new RecoloredImage(resampled, cm);
+        } else if (!(resampled instanceof ResampledImage)) {
+            return resampled;                                   // Do not cache user-provided
image.
         }
         return unique(resampled);
     }
@@ -551,4 +557,47 @@ public class ImageProcessor {
             }
         }
     }
+
+    /**
+     * Returns {@code true} if the given object is an image processor
+     * of the same class with the same configuration.
+     *
+     * @param  object  the other object to compare with this processor.
+     * @return whether the other object is an image processor of the same class with the
same configuration.
+     */
+    @Override
+    public boolean equals(final Object object) {
+        if (object != null && object.getClass() == getClass()) {
+            final ImageProcessor other = (ImageProcessor) object;
+            return errorAction.equals(other.errorAction)   &&
+                 executionMode.equals(other.executionMode) &&
+                 interpolation.equals(other.interpolation) &&
+                 Arrays.equals(fillValues, other.fillValues);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash code value for this image processor based on its current configuration.
+     *
+     * @return a hash code value for this processor.
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(getClass(), errorAction, executionMode, interpolation) + 37*Arrays.hashCode(fillValues);
+    }
+
+    /**
+     * Returns an image processor with the same configuration than this processor.
+     *
+     * @return a clone of this image processor.
+     */
+    @Override
+    public ImageProcessor clone() {
+        try {
+            return (ImageProcessor) super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError(e);
+        }
+    }
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
index 0417cc8..2c2e9dc 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
@@ -19,7 +19,6 @@ package org.apache.sis.coverage.grid;
 import java.util.Random;
 import java.awt.image.DataBuffer;
 import java.awt.image.RenderedImage;
-import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.geometry.ImmutableEnvelope;
@@ -76,47 +75,55 @@ public final strictfp class ResampledGridCoverageTest {
     }
 
     /**
+     * Returns a resampled coverage using processor with default configuration.
+     * We use processor instead than instantiating {@link ResampledGridCoverage} directly
in order
+     * to test {@link GridCoverageProcessor#resample(GridCoverage, GridGeometry)} method
as well.
+     */
+    private static GridCoverage resample(final GridCoverage source, final GridGeometry target)
throws TransformException {
+        final GridCoverageProcessor processor = new GridCoverageProcessor();
+        processor.setInterpolation(Interpolation.NEAREST);
+        return processor.resample(source, target);
+    }
+
+    /**
      * Tests application of an identity transform from an explicitly specified grid geometry.
      * We expect the source coverage to be returned unchanged.
      *
-     * @throws FactoryException if transformation between CRS can not be computed.
      * @throws TransformException if some coordinates can not be transformed to the target
grid geometry.
      */
     @Test
-    public void testExplicitIdentity() throws FactoryException, TransformException {
+    public void testExplicitIdentity() throws TransformException {
         final GridCoverage2D source = createGridCoverage();
         GridGeometry gg = source.getGridGeometry();
         gg = new GridGeometry(null, CELL_CENTER, gg.getGridToCRS(CELL_CENTER), gg.getCoordinateReferenceSystem());
-        final GridCoverage target = ResampledGridCoverage.create(source, gg, Interpolation.NEAREST);
+        final GridCoverage target = resample(source, gg);
         assertSame("Identity transform should result in same coverage.", source, target);
     }
 
     /**
      * Tests application of an identity transform without specifying explicitly the desired
grid geometry.
      *
-     * @throws FactoryException if transformation between CRS can not be computed.
      * @throws TransformException if some coordinates can not be transformed to the target
grid geometry.
      */
     @Test
-    public void testImplicitIdentity() throws FactoryException, TransformException {
+    public void testImplicitIdentity() throws TransformException {
         final GridCoverage2D source = createGridCoverage();
         GridGeometry gg = source.getGridGeometry();
         gg = new GridGeometry(null, CELL_CENTER, null, gg.getCoordinateReferenceSystem());
-        final GridCoverage target = ResampledGridCoverage.create(source, gg, Interpolation.NEAREST);
+        final GridCoverage target = resample(source, gg);
         assertSame("Identity transform should result in same coverage.", source, target);
     }
 
     /**
      * Tests application of an axis swapping.
      *
-     * @throws FactoryException if transformation between CRS can not be computed.
      * @throws TransformException if some coordinates can not be transformed to the target
grid geometry.
      */
     @Test
-    public void testAxisSwap() throws FactoryException, TransformException {
+    public void testAxisSwap() throws TransformException {
         final GridCoverage2D source = createGridCoverage();
         GridGeometry gg = new GridGeometry(null, CELL_CENTER, null, HardCodedCRS.WGS84_φλ);
-        final GridCoverage target = ResampledGridCoverage.create(source, gg, Interpolation.NEAREST);
+        final GridCoverage target = resample(source, gg);
         /*
          * We expect the same image since `ResampledGridCoverage` should have been
          * able to apply the operation with only a change of `gridToCRS` transform.
@@ -147,14 +154,13 @@ public final strictfp class ResampledGridCoverageTest {
     /**
      * Tests resampling of a sub-region.
      *
-     * @throws FactoryException if transformation between CRS can not be computed.
      * @throws TransformException if some coordinates can not be transformed to the target
grid geometry.
      */
     @Test
-    public void testSubArea() throws FactoryException, TransformException {
+    public void testSubArea() throws TransformException {
         final GridCoverage2D source = createGridCoverage();           // Envelope2D(20, 15,
60, 62)
         GridGeometry gg = new GridGeometry(null, new Envelope2D(HardCodedCRS.WGS84, 18, 20,
17, 31));
-        final GridCoverage target = ResampledGridCoverage.create(source, gg, Interpolation.NEAREST);
+        final GridCoverage target = resample(source, gg);
         final GridExtent sourceExtent = source.getGridGeometry().getExtent();
         final GridExtent targetExtent = target.getGridGeometry().getExtent();
         assertTrue(sourceExtent.getSize(0) > targetExtent.getSize(0));
@@ -164,14 +170,13 @@ public final strictfp class ResampledGridCoverageTest {
     /**
      * Tests application of a reprojection.
      *
-     * @throws FactoryException if transformation between CRS can not be computed.
      * @throws TransformException if some coordinates can not be transformed to the target
grid geometry.
      */
     @Test
-    public void testReprojection() throws FactoryException, TransformException {
+    public void testReprojection() throws TransformException {
         final GridCoverage2D source = createGridCoverage();
         GridGeometry gg = new GridGeometry(null, CELL_CENTER, null, HardCodedConversions.mercator());
-        final GridCoverage target = ResampledGridCoverage.create(source, gg, Interpolation.NEAREST);
+        final GridCoverage target = resample(source, gg);
         assertTrue("GridExtent.startsAtZero", target.getGridGeometry().getExtent().startsAtZero());
         /*
          * Mercator projection does not change pixel width, but change pixel height.
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java
index b3d9d10..5585cde 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java
@@ -91,7 +91,9 @@ public final strictfp class ResampledImageTest extends TestCase {
 
     /**
      * Creates an interpolated image in the simple case where the image is scaled by a factor
2.
-     * The {@link #source} field must be initialized before this method is invoked.
+     * The {@link #source} field must be initialized before this method is invoked. This
method
+     * uses processor instead than instantiating {@link ResampledImage} directly for testing
the
+     * {@link ImageProcessor#resample(Rectangle, MathTransform, RenderedImage)} method as
well.
      *
      * @param  interpolation  the interpolation method to test.
      * @param  minX  minimal X coordinate to give to the resampled image.
@@ -105,7 +107,9 @@ public final strictfp class ResampledImageTest extends TestCase {
         final AffineTransform tr = AffineTransform.getTranslateInstance(source.getMinX(),
source.getMinY());
         tr.scale(0.5, 0.5);
         tr.translate(-bounds.x, -bounds.y);
-        target = new ResampledImage(bounds, new AffineTransform2D(tr), source, interpolation,
null);
+        final ImageProcessor processor = new ImageProcessor();
+        processor.setInterpolation(interpolation);
+        target = (ResampledImage) processor.resample(bounds, new AffineTransform2D(tr), source);
 
         tr.invert();
         sourceToTarget = tr;


Mime
View raw message