From commits-return-13426-apmail-sis-commits-archive=sis.apache.org@sis.apache.org Thu Apr 2 20:45:37 2020
Return-Path:
X-Original-To: apmail-sis-commits-archive@www.apache.org
Delivered-To: apmail-sis-commits-archive@www.apache.org
Received: from mail.apache.org (hermes.apache.org [207.244.88.153])
by minotaur.apache.org (Postfix) with SMTP id 6CA961936C
for ; Thu, 2 Apr 2020 20:45:37 +0000 (UTC)
Received: (qmail 2946 invoked by uid 500); 2 Apr 2020 20:45:37 -0000
Delivered-To: apmail-sis-commits-archive@sis.apache.org
Received: (qmail 2923 invoked by uid 500); 2 Apr 2020 20:45:36 -0000
Mailing-List: contact commits-help@sis.apache.org; run by ezmlm
Precedence: bulk
List-Help:
List-Unsubscribe:
List-Post:
List-Id:
Reply-To: sis-dev@sis.apache.org
Delivered-To: mailing list commits@sis.apache.org
Received: (qmail 2914 invoked by uid 99); 2 Apr 2020 20:45:36 -0000
Received: from ec2-52-202-80-70.compute-1.amazonaws.com (HELO gitbox.apache.org) (52.202.80.70)
by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 02 Apr 2020 20:45:36 +0000
Received: by gitbox.apache.org (ASF Mail Server at gitbox.apache.org, from userid 33)
id 907E4819F7; Thu, 2 Apr 2020 20:45:36 +0000 (UTC)
Date: Thu, 02 Apr 2020 20:45:36 +0000
To: "commits@sis.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.
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit
Message-ID: <158586033653.31661.598481579152862525@gitbox.apache.org>
From: desruisseaux@apache.org
X-Git-Host: gitbox.apache.org
X-Git-Repo: sis
X-Git-Refname: refs/heads/geoapi-4.0
X-Git-Reftype: branch
X-Git-Oldrev: bfe55833ff7f9641fec438fe4c02f54626933496
X-Git-Newrev: cf9962ab9b3e6d71fb03e5868027b5475abed9a8
X-Git-Rev: cf9962ab9b3e6d71fb03e5868027b5475abed9a8
X-Git-NotificationType: ref_changed_plus_diff
X-Git-Multimail-Version: 1.5.dev
Auto-Submitted: auto-generated
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
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 {
*
*
* 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 inverse of the transform from Source Grid
+ * to Target Grid. That transform will be computed using the following path:
+ *
+ * Target Grid ⟶ Target CRS ⟶ Source CRS ⟶ Source Grid
+ *
+ * If the target {@link GridGeometry} is incomplete, this method provides default
+ * values for the missing properties. The following cases may occur:
+ *
+ *
+ * -
+ * 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 Source Grid → Target Grid transform.
+ *
-
+ * 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.
+ *
-
+ * 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.
+ *
*
* @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;
*
*
* -
+ * {@linkplain #setInterpolation(Interpolation) Interpolation method} to use during resampling operations.
+ *
-
+ * {@linkplain #setFillValues(Number...) Fill values} to use for pixels that can not be computed.
+ *
-
* 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.
- *
-
- * Interpolation methods to use during resampling operations and fill values to use for pixels that can not
- * be computed.
*
*
*
@@ -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 {
*
* 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.
+ * The intent is to avoid resampling a resampled image; instead this method tries to work on the original data.
*
* @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.
*
+ * Note:
+ * 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.
+ *
* @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.