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