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