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: Interpolation should be done on floating point values when they exist. This is required for handling correctly the missing values.
Date Thu, 02 Apr 2020 20:45:36 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 cf9962a  Interpolation should be done on floating point values when they exist. This
is required for handling correctly the missing values.
cf9962a is described below

commit cf9962ab9b3e6d71fb03e5868027b5475abed9a8
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Thu Apr 2 15:40:56 2020 +0200

    Interpolation should be done on floating point values when they exist.
    This is required for handling correctly the missing values.
---
 .../sis/coverage/grid/ConvertedGridCoverage.java   |  2 +-
 .../sis/coverage/grid/GridCoverageProcessor.java   | 42 +++++++++++----
 .../org/apache/sis/coverage/grid/GridGeometry.java | 33 +++++++-----
 .../sis/coverage/grid/ResampledGridCoverage.java   | 60 +++++++++++++++++++++-
 .../java/org/apache/sis/image/ImageProcessor.java  | 16 ++++--
 5 files changed, 125 insertions(+), 28 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
index 9a655b2..6ce1f11 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
@@ -59,7 +59,7 @@ final class ConvertedGridCoverage extends GridCoverage {
      * The coverage containing source values.
      * Sample values will be converted from that coverage using the {@link #converters}.
      */
-    private final GridCoverage source;
+    final GridCoverage source;
 
     /**
      * Conversions from {@link #source} values to converted values.
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 6423318..8b9f477 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
@@ -58,26 +58,27 @@ public class GridCoverageProcessor implements Cloneable {
     }
 
     /**
-     * The image processor to use for resampling operations.
+     * The processor to use for operations on two-dimensional slices.
      */
-    private final ImageProcessor imageProcessor;
+    protected final ImageProcessor imageProcessor;
 
     /**
-     * Creates a new set of grid coverage operations with default configuration.
+     * Creates a new processor with default configuration.
      */
     public GridCoverageProcessor() {
         imageProcessor = new ImageProcessor();
     }
 
     /**
-     * Creates a new set of grid coverage operations with the given configuration.
+     * Creates a new processor initialized to the given configuration.
      *
-     * @param  processor  the processor to use for image operations.
+     * @param  processor  the processor to use for operations on two-dimensional slices.
      */
     public GridCoverageProcessor(final ImageProcessor processor) {
         ArgumentChecks.ensureNonNull("processor", processor);
-        imageProcessor = processor;
+        imageProcessor = processor.clone();
     }
+
     /**
      * Returns the interpolation method to use for resampling operations.
      *
@@ -120,6 +121,8 @@ public class GridCoverageProcessor implements Cloneable {
      * </table>
      *
      * The interpolation method can be specified by {@link #setInterpolation(Interpolation)}.
+     * If the grid coverage values are themselves interpolated, this method tries to use
the
+     * original data. The intent is to avoid adding interpolations on top of other interpolations.
      *
      * @param  source  the grid coverage to resample.
      * @param  target  the desired geometry of returned grid coverage. May be incomplete.
@@ -131,15 +134,35 @@ public class GridCoverageProcessor implements Cloneable {
     public GridCoverage resample(GridCoverage source, final GridGeometry target) throws TransformException
{
         ArgumentChecks.ensureNonNull("source", source);
         ArgumentChecks.ensureNonNull("target", target);
+        final boolean isConverted = source == source.forConvertedValues(true);
         /*
          * If the source coverage is already the result of a previous "resample" operation,
          * use the original data in order to avoid interpolating values that are already
interpolated.
          */
-        if (source instanceof ResampledGridCoverage) {
-            source = ((ResampledGridCoverage) source).source;
+        for (;;) {
+            if (ResampledGridCoverage.equivalent(source.getGridGeometry(), target)) {
+                return source;
+            } else if (source instanceof ResampledGridCoverage) {
+                source = ((ResampledGridCoverage) source).source;
+            } else if (source instanceof ConvertedGridCoverage) {
+                source = ((ConvertedGridCoverage) source).source;
+            } else {
+                break;
+            }
+        }
+        /*
+         * The projection are usually applied on floating-point values, in order
+         * to gets maximal precision and to handle correctly the special case of
+         * NaN values. However, we can apply the projection on integer values if
+         * the interpolation type is "nearest neighbor" since this is not really
+         * an interpolation.
+         */
+        if (!Interpolation.NEAREST.equals(imageProcessor.getInterpolation())) {
+            source = source.forConvertedValues(true);
         }
+        final GridCoverage resampled;
         try {
-            return ResampledGridCoverage.create(source, target, imageProcessor);
+            resampled = ResampledGridCoverage.create(source, target, imageProcessor);
         } catch (IllegalGridGeometryException e) {
             final Throwable cause = e.getCause();
             if (cause instanceof TransformException) {
@@ -150,6 +173,7 @@ public class GridCoverageProcessor implements Cloneable {
         } catch (FactoryException e) {
             throw new TransformException(e);
         }
+        return resampled.forConvertedValues(isConverted);
     }
 
     /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index bc2d7c8..c96e82c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -1388,7 +1388,6 @@ public class GridGeometry implements LenientComparable, Serializable
{
      * @since 1.1
      */
     @Override
-    @SuppressWarnings("fallthrough")
     public boolean equals(final Object object, final ComparisonMode mode) {
         if (object == this) {
             return true;
@@ -1407,17 +1406,7 @@ public class GridGeometry implements LenientComparable, Serializable
{
                         Utilities.deepEquals(getCoordinateReferenceSystem(envelope),
                                              getCoordinateReferenceSystem(othenv), mode))
                 {
-                    if (envelope != null) {
-                        for (int i=envelope.getDimension(); --i >= 0;) {
-                            final double ε = (resolution != null) ? resolution[i] * Numerics.COMPARISON_THRESHOLD
: 0;
-                            if (!MathFunctions.epsilonEqual(envelope.getLower(i), othenv.getLower(i),
ε) ||
-                                !MathFunctions.epsilonEqual(envelope.getUpper(i), othenv.getUpper(i),
ε))
-                            {
-                                return false;
-                            }
-                        }
-                    }
-                    return true;
+                    return equalsApproximately(othenv);
                 }
             }
         }
@@ -1425,6 +1414,26 @@ public class GridGeometry implements LenientComparable, Serializable
{
     }
 
     /**
+     * Returns whether the given envelope is equal to this grid geometry envelope with a
tolerance threshold
+     * computed from grid resolution. If this grid geometry has no envelope, then this method
arbitrarily
+     * returns {@code true} (this unusual behavior is required by {@link #equals(Object,
ComparisonMode)}).
+     */
+    final boolean equalsApproximately(final ImmutableEnvelope othenv) {
+        if (envelope != null) {
+            for (int i=envelope.getDimension(); --i >= 0;) {
+                // Arbitrary threshold of ½ pixel.
+                final double ε = (resolution != null) ? resolution[i] * 0.5 : 0;
+                if (!MathFunctions.epsilonEqual(envelope.getLower(i), othenv.getLower(i),
ε) ||
+                    !MathFunctions.epsilonEqual(envelope.getUpper(i), othenv.getUpper(i),
ε))
+                {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
      * Returns the default set of flags to use for {@link #toString()} implementations.
      * Current implementation returns all flags, but future implementation may omit some
      * flags if experience suggests that they are too verbose in practice.
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 d8b149a..1fe621d 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
@@ -41,6 +41,7 @@ import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.Utilities;
 
 
 /**
@@ -177,7 +178,64 @@ final class ResampledGridCoverage extends GridCoverage {
     }
 
     /**
+     * Checks if two grid geometries are equal, ignoring unspecified properties. If a geometry
+     * has no extent or no {@code gridToCRS} transform, the missing property is not compared.
+     * Same applies for the grid extent.
+     *
+     * @return {@code true} if the two geometries are equal, ignoring unspecified properties.
+     */
+    static boolean equivalent(final GridGeometry sourceGG, final GridGeometry targetGG) {
+        return (!isDefined(sourceGG, targetGG, GridGeometry.EXTENT)
+                || Utilities.equalsIgnoreMetadata(sourceGG.getExtent(),
+                                                  targetGG.getExtent()))
+            && (!isDefined(sourceGG, targetGG, GridGeometry.CRS)
+                || Utilities.equalsIgnoreMetadata(sourceGG.getCoordinateReferenceSystem(),
+                                                  targetGG.getCoordinateReferenceSystem()))
+            && (!isDefined(sourceGG, targetGG, GridGeometry.GRID_TO_CRS)
+                || Utilities.equalsIgnoreMetadata(sourceGG.getGridToCRS(PixelInCell.CELL_CORNER),
+                                                  targetGG.getGridToCRS(PixelInCell.CELL_CORNER))
+                || Utilities.equalsIgnoreMetadata(sourceGG.getGridToCRS(PixelInCell.CELL_CENTER),
  // Its okay if only one is equal.
+                                                  targetGG.getGridToCRS(PixelInCell.CELL_CENTER)))
+            && (!isDefined(sourceGG, targetGG, GridGeometry.ENVELOPE)
+              || isDefined(sourceGG, targetGG, GridGeometry.EXTENT | GridGeometry.GRID_TO_CRS)
     // Compare only if not inferred.
+              || sourceGG.equalsApproximately(targetGG.envelope));
+    }
+
+    /**
+     * Returns whether the given property is defined in both grid geometries.
+     *
+     * @param  property  one of {@link GridGeometry} constants.
+     */
+    private static boolean isDefined(final GridGeometry sourceGG, final GridGeometry targetGG,
final int property) {
+        return targetGG.isDefined(property) && sourceGG.isDefined(property);
+    }
+
+    /**
      * Implementation of {@link GridCoverageProcessor#resample(GridCoverage, GridGeometry)}.
+     * This method computes the <em>inverse</em> of the transform from <var>Source
Grid</var>
+     * to <var>Target Grid</var>. That transform will be computed using the following
path:
+     *
+     * <blockquote>Target Grid  ⟶  Target CRS  ⟶  Source CRS  ⟶  Source Grid</blockquote>
+     *
+     * If the target {@link GridGeometry} is incomplete, this method provides default
+     * values for the missing properties. The following cases may occur:
+     *
+     * <ul class="verbose">
+     *   <li>
+     *     User provided no {@link GridExtent}. This method will construct a "grid to CRS"
transform
+     *     preserving (at least approximately) axis directions and resolutions at the point
of interest.
+     *     Then a grid extent will be created with a size large enough for containing the
original grid
+     *     transformed by above <var>Source Grid</var> → <var>Target
Grid</var> transform.
+     *   </li><li>
+     *     User provided only a {@link GridExtent}. This method will compute an envelope
large enough
+     *     for containing the projected coordinates, then a "grid to CRS" transform will
be derived
+     *     from the grid and the georeferenced envelope with an attempt to preserve axis
directions
+     *     at least approximately.
+     *   </li><li>
+     *     User provided only a "grid to CRS" transform. This method will transform the projected
envelope
+     *     to "grid units" using the specified transform and create a grid extent large enough
to hold the
+     *     result.</li>
+     * </ul>
      *
      * @param  source  the grid coverage to resample.
      * @param  target  the desired geometry of returned grid coverage. May be incomplete.
@@ -201,7 +259,7 @@ final class ResampledGridCoverage extends GridCoverage {
         if (sourceGG.isDefined(GridGeometry.ENVELOPE) && target.isDefined(GridGeometry.ENVELOPE))
{
             changeOfCRS = Envelopes.findOperation(sourceGG.getEnvelope(), target.getEnvelope());
         }
-        if (changeOfCRS == null) try {
+        if (changeOfCRS == null && sourceCRS != null && targetCRS != null)
try {
             DefaultGeographicBoundingBox areaOfInterest = null;
             if (sourceGG.isDefined(GridGeometry.ENVELOPE)) {
                 areaOfInterest = new DefaultGeographicBoundingBox();
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 364a29a..1d93555 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
@@ -47,6 +47,10 @@ import org.apache.sis.referencing.operation.transform.MathTransforms;
  *
  * <ul class="verbose">
  *   <li>
+ *     {@linkplain #setInterpolation(Interpolation) Interpolation method} to use during resampling
operations.
+ *   </li><li>
+ *     {@linkplain #setFillValues(Number...) Fill values} to use for pixels that can not
be computed.
+ *   </li><li>
  *     Whether operations can be executed in parallel. By default operations on unknown
  *     {@link RenderedImage} implementations are executed sequentially in the caller thread,
for safety reasons.
  *     Some operations can be parallelized, but it should be enabled only if the {@link RenderedImage}
is known
@@ -58,9 +62,6 @@ 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>
  *
@@ -211,7 +212,7 @@ public class ImageProcessor implements Cloneable {
     }
 
     /**
-     * Creates a new set of image operations with default configuration.
+     * Creates a new processor with default configuration.
      * The execution mode is initialized to {@link Mode#DEFAULT} and the error action to
{@link ErrorAction#THROW}.
      */
     public ImageProcessor() {
@@ -448,7 +449,7 @@ public class ImageProcessor implements Cloneable {
      *
      * <p>If the given source is an instance of {@link ResampledImage} or {@link AnnotatedImage},
      * then this method will use {@linkplain PlanarImage#getSources() the source} of the
given source.
-     * The intent is to avoid resampling a resampled image and try to work on the original
data instead.</p>
+     * The intent is to avoid resampling a resampled image; instead this method tries to
work on the original data.</p>
      *
      * @param  bounds    domain of pixel coordinates of resampled image.
      * @param  toSource  conversion of pixel coordinates of this image to pixel coordinates
of {@code source} image.
@@ -500,6 +501,11 @@ public class ImageProcessor implements Cloneable {
      * Computes all tiles immediately, then return an image will all tiles ready.
      * Computations will use many threads if {@linkplain #getExecutionMode() execution mode}
is parallel.
      *
+     * <div class="note"><b>Note:</b>
+     * current implementation ignores the {@linkplain #getErrorAction() error action} because
we do not yet
+     * have a mechanism for specifying which tile to produce in replacement of tiles that
can not be computed.
+     * This behavior may be changed in a future version.</div>
+     *
      * @param  source  the image to compute immediately (may be {@code null}).
      * @return image with all tiles computed, or {@code null} if the given image was null.
      * @throws ImagingOpException if an exception occurred during {@link RenderedImage#getTile(int,
int)} call.


Mime
View raw message