You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/04/08 16:38:08 UTC
[sis] 02/03: Add a `MultiSourceImage` package-private abstract class and add support for prefetch operation.
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
commit d3164ba70d09272dea3950f305dfc45f404bae18
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Apr 8 15:29:12 2023 +0200
Add a `MultiSourceImage` package-private abstract class and add support for prefetch operation.
---
.../coverage/grid/BandAggregateGridCoverage.java | 4 +-
.../sis/coverage/grid/GridCoverageProcessor.java | 4 +-
.../org/apache/sis/image/BandAggregateImage.java | 100 ++----------
.../java/org/apache/sis/image/BandSharing.java | 31 ++--
.../java/org/apache/sis/image/ImageProcessor.java | 6 +-
.../org/apache/sis/image/MultiSourceImage.java | 159 ++++++++++++++++++
...inedImageLayout.java => MultiSourceLayout.java} | 12 +-
.../org/apache/sis/image/MultiSourcePrefetch.java | 178 +++++++++++++++++++++
...urcesArgument.java => MultiSourceArgument.java} | 4 +-
.../apache/sis/image/BandAggregateImageTest.java | 37 ++++-
.../aggregate/BandAggregateGridResource.java | 4 +-
11 files changed, 416 insertions(+), 123 deletions(-)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java
index 6b95c83080..113033a9fb 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java
@@ -24,7 +24,7 @@ import org.opengis.referencing.operation.TransformException;
import org.apache.sis.image.DataType;
import org.apache.sis.image.ImageProcessor;
import org.apache.sis.internal.feature.Resources;
-import org.apache.sis.internal.coverage.MultiSourcesArgument;
+import org.apache.sis.internal.coverage.MultiSourceArgument;
import org.apache.sis.internal.util.CollectionsExt;
@@ -89,7 +89,7 @@ final class BandAggregateGridCoverage extends GridCoverage {
* @throws IllegalArgumentException if there is an incompatibility between some source coverages
* or if some band indices are duplicated or outside their range of validity.
*/
- BandAggregateGridCoverage(final MultiSourcesArgument<GridCoverage> aggregate, final ImageProcessor processor) {
+ BandAggregateGridCoverage(final MultiSourceArgument<GridCoverage> aggregate, final ImageProcessor processor) {
super(aggregate.domain(GridCoverage::getGridGeometry), aggregate.ranges());
this.sources = aggregate.sources();
this.bandsPerSource = aggregate.bandsPerSource();
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 a57a73b479..f7f9017fc9 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
@@ -39,7 +39,7 @@ import org.apache.sis.image.Colorizer;
import org.apache.sis.image.ImageProcessor;
import org.apache.sis.image.Interpolation;
import org.apache.sis.internal.coverage.SampleDimensions;
-import org.apache.sis.internal.coverage.MultiSourcesArgument;
+import org.apache.sis.internal.coverage.MultiSourceArgument;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.collection.WeakHashSet;
@@ -761,7 +761,7 @@ public class GridCoverageProcessor implements Cloneable {
* @since 1.4
*/
public GridCoverage aggregateRanges(GridCoverage[] sources, int[][] bandsPerSource) {
- final var aggregate = new MultiSourcesArgument<>(sources, bandsPerSource);
+ final var aggregate = new MultiSourceArgument<>(sources, bandsPerSource);
aggregate.identityAsNull();
aggregate.validate(GridCoverage::getSampleDimensions);
if (aggregate.isIdentity()) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/BandAggregateImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/BandAggregateImage.java
index 46c0a540cf..7ffbd60648 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/BandAggregateImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/BandAggregateImage.java
@@ -16,8 +16,6 @@
*/
package org.apache.sis.image;
-import java.util.Arrays;
-import java.util.Objects;
import java.awt.Rectangle;
import java.awt.image.ColorModel;
import java.awt.image.BandedSampleModel;
@@ -43,34 +41,7 @@ import org.apache.sis.internal.coverage.j2d.ImageUtilities;
*
* @since 1.4
*/
-class BandAggregateImage extends WritableComputedImage {
- /**
- * The source images with only the bands to aggregate, in order.
- * Those images are views; the band sample values are not copied.
- */
- protected final RenderedImage[] filteredSources;
-
- /**
- * Color model of the aggregated image.
- *
- * @see #getColorModel()
- */
- private final ColorModel colorModel;
-
- /**
- * Domain of pixel coordinates. All images shall share the same pixel coordinate space,
- * meaning that a pixel at coordinates (<var>x</var>, <var>y</var>) in this image will
- * contain the sample values of all source images at the same coordinates.
- * It does <em>not</em> mean that all source images shall have the same bounds.
- */
- private final int minX, minY, width, height;
-
- /**
- * Index of the first tile. Contrarily to pixel coordinates,
- * the tile coordinate space does not need to be the same for all images.
- */
- private final int minTileX, minTileY;
-
+class BandAggregateImage extends MultiSourceImage {
/**
* Whether the sharing of data arrays is allowed.
* When a source tile has the same bounds and scanline stride than the target tile,
@@ -87,19 +58,20 @@ class BandAggregateImage extends WritableComputedImage {
* @param bandsPerSource bands to use for each source image, in order. May contain {@code null} elements.
* @param colorizer provider of color model to use for this image, or {@code null} for automatic.
* @param allowSharing whether to allow the sharing of data buffers (instead of copying) if possible.
+ * @param parallel whether parallel computation is allowed.
* @throws IllegalArgumentException if there is an incompatibility between some source images
* or if some band indices are duplicated or outside their range of validity.
* @return the band aggregate image.
*/
static RenderedImage create(final RenderedImage[] sources, final int[][] bandsPerSource,
- final Colorizer colorizer, final boolean allowSharing)
+ final Colorizer colorizer, final boolean allowSharing, final boolean parallel)
{
- final var layout = CombinedImageLayout.create(sources, bandsPerSource, allowSharing);
+ final var layout = MultiSourceLayout.create(sources, bandsPerSource, allowSharing);
final BandAggregateImage image;
if (layout.isWritable()) {
- image = new Writable(layout, colorizer, allowSharing);
+ image = new Writable(layout, colorizer, allowSharing, parallel);
} else {
- image = new BandAggregateImage(layout, colorizer, allowSharing);
+ image = new BandAggregateImage(layout, colorizer, allowSharing, parallel);
}
if (image.filteredSources.length == 1) {
final RenderedImage c = image.filteredSources[0];
@@ -120,30 +92,13 @@ class BandAggregateImage extends WritableComputedImage {
* @param layout pixel and tile coordinate spaces of this image, together with sample model.
* @param colorizer provider of color model to use for this image, or {@code null} for automatic.
*/
- BandAggregateImage(final CombinedImageLayout layout, final Colorizer colorizer, final boolean allowSharing) {
- super(layout.sampleModel, layout.sources);
+ BandAggregateImage(final MultiSourceLayout layout, final Colorizer colorizer,
+ final boolean allowSharing, final boolean parallel)
+ {
+ super(layout, colorizer, parallel);
this.allowSharing = allowSharing;
- final Rectangle r = layout.domain;
- minX = r.x;
- minY = r.y;
- width = r.width;
- height = r.height;
- minTileX = layout.minTileX;
- minTileY = layout.minTileY;
- filteredSources = layout.filteredSources;
- colorModel = layout.createColorModel(colorizer);
- ensureCompatible(colorModel);
}
- /** Returns the information inferred at construction time. */
- @Override public ColorModel getColorModel() {return colorModel;}
- @Override public int getWidth() {return width;}
- @Override public int getHeight() {return height;}
- @Override public int getMinX() {return minX;}
- @Override public int getMinY() {return minY;}
- @Override public int getMinTileX() {return minTileX;}
- @Override public int getMinTileY() {return minTileY;}
-
/**
* Creates a raster containing the selected bands of source images.
*
@@ -159,15 +114,13 @@ class BandAggregateImage extends WritableComputedImage {
/*
* If we are allowed to share the data arrays, try that first.
* The cast to `BandedSampleModel` is safe because this is the
- * type given by `CombinedImageLayout` in the constructor.
+ * type given by `MultiSourceLayout` in the constructor.
*/
BandSharedRaster shared = null;
if (allowSharing) {
final BandSharing sharing = BandSharing.create((BandedSampleModel) sampleModel);
if (sharing != null) {
- final long x = Math.multiplyFull(tileX - minTileX, getTileWidth()) + minX;
- final long y = Math.multiplyFull(tileY - minTileY, getTileHeight()) + minY;
- tile = shared = sharing.createRaster(x, y, filteredSources);
+ tile = shared = sharing.createRaster(tileToPixel(tileX, tileY), filteredSources);
}
}
/*
@@ -206,8 +159,10 @@ class BandAggregateImage extends WritableComputedImage {
* @param layout pixel and tile coordinate spaces of this image, together with sample model.
* @param colorizer provider of color model to use for this image, or {@code null} for automatic.
*/
- Writable(final CombinedImageLayout layout, final Colorizer colorizer, final boolean allowSharing) {
- super(layout, colorizer, allowSharing);
+ Writable(final MultiSourceLayout layout, final Colorizer colorizer,
+ final boolean allowSharing, final boolean parallel)
+ {
+ super(layout, colorizer, allowSharing, parallel);
}
/**
@@ -291,32 +246,11 @@ class BandAggregateImage extends WritableComputedImage {
}
}
- /**
- * Returns a hash code value for this image.
- */
- @Override
- public int hashCode() {
- return sampleModel.hashCode() + 37 * (Arrays.hashCode(filteredSources) + 31 * Objects.hashCode(colorModel));
- }
-
/**
* Compares the given object with this image for equality.
- *
- * <h4>Implementation note</h4>
- * We do not invoke {@link #equalsBase(Object)} for saving the comparisons of {@link ComputedImage#sources} array.
- * The comparison of {@link #filteredSources} array will indirectly include the comparison of raw source images.
*/
@Override
public boolean equals(final Object object) {
- if (object instanceof BandAggregateImage) {
- final BandAggregateImage other = (BandAggregateImage) object;
- return minTileX == other.minTileX &&
- minTileY == other.minTileY &&
- getBounds().equals(other.getBounds()) &&
- sampleModel.equals(other.sampleModel) &&
- Objects.equals(colorModel, other.colorModel) &&
- Arrays.equals(filteredSources, other.filteredSources);
- }
- return false;
+ return super.equals(object) && ((BandAggregateImage) object).allowSharing == allowSharing;
}
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/BandSharing.java b/core/sis-feature/src/main/java/org/apache/sis/image/BandSharing.java
index 29ffb0fba5..4f2bf46e9c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/BandSharing.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/BandSharing.java
@@ -102,12 +102,11 @@ abstract class BandSharing {
* Prepares sharing the arrays of the given sources when possible.
* This method does not allocate new {@link DataBuffer} banks.
*
- * @param x <var>x</var> pixel coordinate of the tile.
- * @param y <var>y</var> pixel coordinate of the tile.
- * @param sources the sources for which to aggregate all bands.
+ * @param location smallest (<var>x</var>,<var>y</var>) pixel coordinates of the tile.
+ * @param sources the sources for which to aggregate all bands.
* @return data buffer size, or 0 if there is nothing to share.
*/
- private int prepare(final long x, final long y, final RenderedImage[] sources) {
+ private int prepare(final Point location, final RenderedImage[] sources) {
final int tileWidth = target.getWidth();
final int tileHeight = target.getHeight();
final int scanlineStride = target.getScanlineStride();
@@ -121,17 +120,15 @@ abstract class BandSharing {
if (source.getTileWidth() == tileWidth &&
source.getTileHeight() == tileHeight)
{
- long tileX = x - source.getTileGridXOffset();
- long tileY = y - source.getTileGridYOffset();
+ int tileX = Math.subtractExact(location.x, source.getTileGridXOffset());
+ int tileY = Math.subtractExact(location.y, source.getTileGridYOffset());
if (((tileX % tileWidth) | (tileY % tileHeight)) == 0) {
tileX /= tileWidth;
tileY /= tileHeight;
- final int tx = Math.toIntExact(tileX);
- final int ty = Math.toIntExact(tileY);
final int n = si << 1;
- sourceTileIndices[n ] = tx;
- sourceTileIndices[n+1] = ty;
- final Raster raster = source.getTile(tx, ty);
+ sourceTileIndices[n ] = tileX;
+ sourceTileIndices[n+1] = tileY;
+ final Raster raster = source.getTile(tileX, tileY);
final SampleModel c = raster.getSampleModel();
if (c instanceof ComponentSampleModel) {
final var sm = (ComponentSampleModel) c;
@@ -172,18 +169,16 @@ abstract class BandSharing {
* Creates a raster sharing the arrays of given sources when possible.
* This method assumes a target {@link BandedSampleModel} where all band offsets.
*
- * @param x <var>x</var> pixel coordinate of the tile.
- * @param y <var>y</var> pixel coordinate of the tile.
- * @param sources the sources for which to aggregate all bands.
- * @return a raster containing the aggregation of all bands, or {@code null} if the is nothing to share.
+ * @param location smallest (<var>x</var>,<var>y</var>) pixel coordinates of the tile.
+ * @param sources the sources for which to aggregate all bands.
+ * @return a raster containing the aggregation of all bands, or {@code null} if there is nothing to share.
*/
- final BandSharedRaster createRaster(final long x, final long y, final RenderedImage[] sources) {
- final int size = prepare(x, y, sources);
+ final BandSharedRaster createRaster(final Point location, final RenderedImage[] sources) {
+ final int size = prepare(location, sources);
if (size == 0) {
return null;
}
final DataBuffer buffer = allocate(size);
- final var location = new Point(Math.toIntExact(x), Math.toIntExact(y));
return new BandSharedRaster(sourceTileIndices, parents, target, buffer, location);
}
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 fab9c30fe6..56238cb8a2 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
@@ -976,6 +976,7 @@ public class ImageProcessor implements Cloneable {
* This operation uses the following properties in addition to method parameters:
* <ul>
* <li>{@linkplain #getColorizer() Colorizer}.</li>
+ * <li>{@linkplain #getExecutionMode() Execution mode} (parallel or sequential).</li>
* </ul>
*
* @param sources images whose bands shall be aggregated, in order. At least one image must be provided.
@@ -989,10 +990,12 @@ public class ImageProcessor implements Cloneable {
public RenderedImage aggregateBands(final RenderedImage[] sources, final int[][] bandsPerSource) {
ArgumentChecks.ensureNonEmpty("sources", sources);
final Colorizer colorizer;
+ final boolean parallel;
synchronized (this) {
colorizer = this.colorizer;
+ parallel = executionMode != Mode.SEQUENTIAL;
}
- return unique(BandAggregateImage.create(sources, bandsPerSource, colorizer, true));
+ return unique(BandAggregateImage.create(sources, bandsPerSource, colorizer, true, parallel));
}
/**
@@ -1503,6 +1506,7 @@ public class ImageProcessor implements Cloneable {
* @throws ImagingOpException if an error occurred during calculation.
*/
public List<NavigableMap<Double,Shape>> isolines(final RenderedImage data, final double[][] levels, final MathTransform gridToCRS) {
+ ArgumentChecks.ensureNonNull("data", data);
final boolean parallel;
synchronized (this) {
parallel = parallel(data);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/MultiSourceImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/MultiSourceImage.java
new file mode 100644
index 0000000000..ed67b5446c
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/MultiSourceImage.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.image;
+
+import java.awt.Point;
+import java.util.Arrays;
+import java.util.Objects;
+import java.awt.Rectangle;
+import java.awt.image.ColorModel;
+import java.awt.image.RenderedImage;
+import java.awt.image.WritableRenderedImage;
+import org.apache.sis.internal.coverage.j2d.ImageUtilities;
+import org.apache.sis.util.Disposable;
+
+
+/**
+ * An image which is the result of a computation involving more than one source.
+ * All sources shall use the same pixel coordinate system. However the sources
+ * do not need to have the same bounds or use the same tile matrix.
+ *
+ * <p>This implementation is for images that are <em>potentially</em> writable.
+ * Whether the image is effectively writable depends on whether all sources are
+ * instances of {@link WritableRenderedImage}.</p>
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.4
+ * @since 1.4
+ */
+abstract class MultiSourceImage extends WritableComputedImage {
+ /**
+ * The source images, potentially with a preprocessing applied.
+ * Those sources may be different than {@link #getSources()} for example with the
+ * application of a "band select" operation for retaining only the bands needed.
+ */
+ protected final RenderedImage[] filteredSources;
+
+ /**
+ * Color model of this image.
+ *
+ * @see #getColorModel()
+ */
+ protected final ColorModel colorModel;
+
+ /**
+ * Domain of pixel coordinates. All images shall share the same pixel coordinate space,
+ * meaning that a pixel at coordinates (<var>x</var>, <var>y</var>) in this image will
+ * contain the sample values of all source images at the same coordinates.
+ * It does <em>not</em> mean that all source images shall have the same bounds.
+ */
+ private final int minX, minY, width, height;
+
+ /**
+ * Index of the first tile. Contrarily to pixel coordinates,
+ * the tile coordinate space does not need to be the same for all images.
+ */
+ private final int minTileX, minTileY;
+
+ /**
+ * Whether parallel computation is allowed.
+ */
+ private final boolean parallel;
+
+ /**
+ * Creates a new multi-sources image.
+ *
+ * @param layout pixel and tile coordinate spaces of this image, together with sample model.
+ * @param colorizer provider of color model to use for this image, or {@code null} for automatic.
+ * @param parallel whether parallel computation is allowed.
+ */
+ MultiSourceImage(final MultiSourceLayout layout, final Colorizer colorizer, final boolean parallel) {
+ super(layout.sampleModel, layout.sources);
+ final Rectangle r = layout.domain;
+ minX = r.x;
+ minY = r.y;
+ width = r.width;
+ height = r.height;
+ minTileX = layout.minTileX;
+ minTileY = layout.minTileY;
+ filteredSources = layout.filteredSources;
+ colorModel = layout.createColorModel(colorizer);
+ ensureCompatible(colorModel);
+ this.parallel = parallel;
+ }
+
+ /** Returns the information inferred at construction time. */
+ @Override public final ColorModel getColorModel() {return colorModel;}
+ @Override public final int getWidth() {return width;}
+ @Override public final int getHeight() {return height;}
+ @Override public final int getMinX() {return minX;}
+ @Override public final int getMinY() {return minY;}
+ @Override public final int getMinTileX() {return minTileX;}
+ @Override public final int getMinTileY() {return minTileY;}
+
+ /**
+ * Converts a tile (column, row) indices to smallest (<var>x</var>, <var>y</var>) pixel coordinates
+ * inside the tile. The returned value is a coordinate of the pixel in upper-left corner.
+ *
+ * @param tileX the tile index for which to get pixel coordinate.
+ * @param tileY the tile index for which to get pixel coordinate.
+ * @return smallest (<var>x</var>, <var>y</var>) pixel coordinates inside the tile.
+ */
+ final Point tileToPixel(final int tileX, final int tileY) {
+ return new Point(Math.toIntExact((((long) tileX) - minTileX) * getTileWidth() + minX),
+ Math.toIntExact((((long) tileY) - minTileY) * getTileHeight() + minY));
+ }
+
+ /**
+ * Notifies the source images that tiles will be computed soon in the given region.
+ * This method forwards the notification to all images that are instances of {@link PlanarImage}.
+ */
+ @Override
+ protected Disposable prefetch(final Rectangle tiles) {
+ /*
+ * Convert tile indices to pixel indices. The latter will be converted back to
+ * tile indices for each source because the tile numbering may not be the same.
+ */
+ final Rectangle aoi = ImageUtilities.tilesToPixels(this, tiles);
+ return new MultiSourcePrefetch(filteredSources, aoi).run(parallel);
+ }
+
+ /**
+ * Returns a hash code value for this image.
+ */
+ @Override
+ public int hashCode() {
+ return hashCodeBase() + 37 * (Arrays.hashCode(filteredSources) + 31 * Objects.hashCode(colorModel));
+ }
+
+ /**
+ * Compares the given object with this image for equality.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (equalsBase(object)) {
+ final MultiSourceImage other = (MultiSourceImage) object;
+ return parallel == other.parallel &&
+ minTileX == other.minTileX &&
+ minTileY == other.minTileY &&
+ getBounds().equals(other.getBounds()) &&
+ Objects.equals(colorModel, other.colorModel) &&
+ Arrays.equals(filteredSources, other.filteredSources);
+ }
+ return false;
+ }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/CombinedImageLayout.java b/core/sis-feature/src/main/java/org/apache/sis/image/MultiSourceLayout.java
similarity index 97%
rename from core/sis-feature/src/main/java/org/apache/sis/image/CombinedImageLayout.java
rename to core/sis-feature/src/main/java/org/apache/sis/image/MultiSourceLayout.java
index 7f283984fe..afa30b76e5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/CombinedImageLayout.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/MultiSourceLayout.java
@@ -33,7 +33,7 @@ import org.apache.sis.internal.feature.Resources;
import org.apache.sis.internal.coverage.j2d.ImageLayout;
import org.apache.sis.internal.coverage.j2d.ImageUtilities;
import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
-import org.apache.sis.internal.coverage.MultiSourcesArgument;
+import org.apache.sis.internal.coverage.MultiSourceArgument;
import org.apache.sis.coverage.grid.DisjointExtentException;
@@ -54,7 +54,7 @@ import org.apache.sis.coverage.grid.DisjointExtentException;
*
* @since 1.4
*/
-final class CombinedImageLayout extends ImageLayout {
+final class MultiSourceLayout extends ImageLayout {
/**
* The source images. This is a copy of the user-specified array,
* except that images associated to an empty set of bands are discarded.
@@ -126,8 +126,8 @@ final class CombinedImageLayout extends ImageLayout {
* or if some band indices are duplicated or outside their range of validity.
*/
@Workaround(library="JDK", version="1.8")
- static CombinedImageLayout create(RenderedImage[] sources, int[][] bandsPerSource, boolean allowSharing) {
- final var aggregate = new MultiSourcesArgument<RenderedImage>(sources, bandsPerSource);
+ static MultiSourceLayout create(RenderedImage[] sources, int[][] bandsPerSource, boolean allowSharing) {
+ final var aggregate = new MultiSourceArgument<RenderedImage>(sources, bandsPerSource);
aggregate.identityAsNull();
aggregate.validate(ImageUtilities::getNumBands);
@@ -202,7 +202,7 @@ final class CombinedImageLayout extends ImageLayout {
final var preferredTileSize = new Dimension((int) cx, (int) cy);
final boolean exactTileSize = ((cx | cy) >>> Integer.SIZE) == 0;
allowSharing &= exactTileSize;
- return new CombinedImageLayout(sources, bandsPerSource, domain, preferredTileSize, exactTileSize,
+ return new MultiSourceLayout(sources, bandsPerSource, domain, preferredTileSize, exactTileSize,
chooseMinTile(tileGridXOffset, domain.x, preferredTileSize.width),
chooseMinTile(tileGridYOffset, domain.y, preferredTileSize.height),
commonDataType, aggregate.numBands(), allowSharing ? scanlineStride : 0);
@@ -219,7 +219,7 @@ final class CombinedImageLayout extends ImageLayout {
* @param scanlineStride common scanline stride if data buffers will be shared, or 0 if no sharing.
* @param numBands number of bands of the image to create.
*/
- private CombinedImageLayout(final RenderedImage[] sources, final int[][] bandsPerSource,
+ private MultiSourceLayout(final RenderedImage[] sources, final int[][] bandsPerSource,
final Rectangle domain, final Dimension preferredTileSize, final boolean exactTileSize,
final int minTileX, final int minTileY, final int commonDataType, final int numBands,
final int scanlineStride)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/MultiSourcePrefetch.java b/core/sis-feature/src/main/java/org/apache/sis/image/MultiSourcePrefetch.java
new file mode 100644
index 0000000000..8873f58c24
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/MultiSourcePrefetch.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.image;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.Callable;
+import java.awt.Rectangle;
+import java.awt.image.RenderedImage;
+import java.awt.image.ImagingOpException;
+import org.apache.sis.util.Disposable;
+import org.apache.sis.internal.system.CommonExecutor;
+import org.apache.sis.internal.coverage.j2d.ImageUtilities;
+
+
+/**
+ * A helper class for forwarding a {@code prefetch(…)} operation to multiple sources.
+ * This implementation assumes that all sources share the same pixel coordinates space.
+ * However the tile matrix does not need to be the same.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.4
+ * @since 1.4
+ */
+final class MultiSourcePrefetch implements Disposable {
+ /**
+ * A filtered list of images on which to prefetch at least one tile.
+ * This array contains only {@link PlanarImage} instances which intersect the area of interest.
+ */
+ private final PlanarImage[] sources;
+
+ /**
+ * Indices of tiles to prefetch for each image in the {@link #sources} array.
+ */
+ private final Rectangle[] tileIndices;
+
+ /**
+ * Number of valid elements in {@link #sources} and {@link #tileIndices} arrays.
+ * Note that it will not be worth to use parallelism if the count is less than two.
+ */
+ private int count;
+
+ /**
+ * Handlers to invoke for releasing resources after the prefetch operation is completed.
+ */
+ private Disposable[] cleaners;
+
+ /**
+ * Number of valid elements in the {@link #cleaners} array.
+ */
+ private int cleanerCount;
+
+ /**
+ * If an error occurred while invoking {@code prefetch(…)} or {@code dispose()} on a source, that error.
+ * An error during disposal will not prevent other handlers to be disposed as well. If errors also occur
+ * during the disposal of other handlers, the other exceptions are added as suppressed exceptions.
+ */
+ private RuntimeException error;
+
+ /**
+ * Prepares (without launching) a prefetch operation using the given source images.
+ *
+ * @param images sources on which to apply a prefetch operation.
+ * @param aoi pixel coordinates of the region to prefetch.
+ */
+ MultiSourcePrefetch(final RenderedImage[] images, final Rectangle aoi) {
+ sources = new PlanarImage[images.length];
+ tileIndices = new Rectangle[images.length];
+ for (final RenderedImage source : images) {
+ if (source instanceof PlanarImage) {
+ Rectangle r = new Rectangle(aoi);
+ ImageUtilities.clipBounds(source, r);
+ r = ImageUtilities.pixelsToTiles(source, r);
+ if (!r.isEmpty()) {
+ tileIndices[count] = r;
+ sources[count++] = (PlanarImage) source;
+ }
+ }
+ }
+ }
+
+ /**
+ * Forwards the prefetchs calls to source images.
+ *
+ * <h4>Implementation note</h4>
+ * In many cases the background threads are not really necessary because {@code prefetch(…)} will
+ * only forward to another {@code prefetch(…)} until we reach a final {@code prefetch(…)} which
+ * happen to be a no-op. But it some cases, that final {@code prefetch(…)} will read tiles from
+ * a TIFF or netCDF file (for example).
+ *
+ * @param parallel whether parallelism is allowed.
+ * @return a handler for disposing resources after prefetch, or {@code null} if none.
+ */
+ final Disposable run(boolean parallel) {
+ switch (count) {
+ case 0: return null;
+ case 1: parallel = false;
+ }
+ @SuppressWarnings({"unchecked","rawtypes"})
+ final var workers = (Future<Disposable>[]) (parallel ? new Future[count] : null);
+ cleaners = new Disposable[count];
+ for (int i=0; i<count; i++) {
+ final PlanarImage source = sources[i];
+ final Rectangle r = tileIndices[i];
+ if (parallel) {
+ Callable<Disposable> worker = () -> source.prefetch(r);
+ workers[i] = CommonExecutor.instance().submit(worker);
+ } else {
+ final Disposable cleaner = source.prefetch(r);
+ if (cleaner != null) {
+ cleaners[cleanerCount++] = cleaner;
+ }
+ }
+ }
+ /*
+ * Block until all background threads finished their work. This is needed because `PrefetchedImage`
+ * will start to query tiles after this method returned, so the source images need to be ready.
+ */
+ if (parallel) {
+ for (final Future<Disposable> worker : workers) try {
+ final Disposable cleaner = worker.get();
+ if (cleaner != null) {
+ cleaners[cleanerCount++] = cleaner;
+ }
+ } catch (Exception e) {
+ addError(e);
+ dispose(); // Will rethrow the exception after disposal.
+ }
+ }
+ switch (cleanerCount) {
+ case 0: return null;
+ case 1: return cleaners[0];
+ default: return this;
+ }
+ }
+
+ /**
+ * Disposes the handlers of all sources.
+ */
+ @Override
+ public void dispose() {
+ for (int i=0; i<cleanerCount; i++) try {
+ cleaners[i].dispose();
+ } catch (Exception e) {
+ addError(e);
+ }
+ if (error != null) {
+ throw error;
+ }
+ }
+
+ /**
+ * Declares that an exception occurred. The exception will be thrown after all handlers have been disposed.
+ * If more than one exception occurs, all additional errors are added as suppressed exceptions.
+ */
+ private void addError(final Exception e) {
+ if (error != null) {
+ error.addSuppressed(e);
+ } else if (e instanceof RuntimeException) {
+ error = (RuntimeException) e;
+ } else {
+ error = (ImagingOpException) new ImagingOpException(e.getMessage()).initCause(e);
+ }
+ }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/MultiSourcesArgument.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/MultiSourceArgument.java
similarity index 99%
rename from core/sis-feature/src/main/java/org/apache/sis/internal/coverage/MultiSourcesArgument.java
rename to core/sis-feature/src/main/java/org/apache/sis/internal/coverage/MultiSourceArgument.java
index 5978d0a5c3..27d82af545 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/MultiSourcesArgument.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/MultiSourceArgument.java
@@ -50,7 +50,7 @@ import org.apache.sis.util.ComparisonMode;
*
* @since 1.4
*/
-public final class MultiSourcesArgument<S> {
+public final class MultiSourceArgument<S> {
/**
* The sources of sample dimensions with empty sources removed.
* After a {@code validate(…)} method has been invoked, this array become a
@@ -101,7 +101,7 @@ public final class MultiSourcesArgument<S> {
* @param sources the sources from which to get the sample dimensions.
* @param bandsPerSource sample dimensions for each source. May contain {@code null} elements.
*/
- public MultiSourcesArgument(final S[] sources, final int[][] bandsPerSource) {
+ public MultiSourceArgument(final S[] sources, final int[][] bandsPerSource) {
this.sources = sources;
this.bandsPerSource = bandsPerSource;
sourceOfGridToCRS = -1;
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/BandAggregateImageTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/BandAggregateImageTest.java
index d3ec94a516..ea7641b54d 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/BandAggregateImageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/BandAggregateImageTest.java
@@ -95,7 +95,7 @@ public final class BandAggregateImageTest extends TestCase {
im2.getRaster().setSamples(0, 0, width, height, 0, IntStream.range(0, width*height).map(s -> s * 2).toArray());
sourceImages = new RenderedImage[] {im1, im2};
- final RenderedImage result = BandAggregateImage.create(sourceImages, null, null, allowSharing);
+ final RenderedImage result = BandAggregateImage.create(sourceImages, null, null, allowSharing, false);
assertNotNull(result);
assertEquals(0, result.getMinTileX());
assertEquals(0, result.getMinTileY());
@@ -188,7 +188,7 @@ public final class BandAggregateImageTest extends TestCase {
initializeAllTiles(im1, im2);
sourceImages = new RenderedImage[] {im1, im2};
- RenderedImage result = BandAggregateImage.create(sourceImages, null, null, allowSharing);
+ RenderedImage result = BandAggregateImage.create(sourceImages, null, null, allowSharing, false);
assertNotNull(result);
assertEquals(minX, result.getMinX());
assertEquals(minY, result.getMinY());
@@ -255,7 +255,7 @@ public final class BandAggregateImageTest extends TestCase {
new int[] {1}, // Take second band of image 1.
null, // Take all bands of image 2.
new int[] {0} // Take first band of image 1.
- }, null, allowSharing);
+ }, null, allowSharing, false);
assertNotNull(result);
assertEquals(minX, result.getMinX());
assertEquals(minY, result.getMinY());
@@ -316,7 +316,7 @@ public final class BandAggregateImageTest extends TestCase {
initializeAllTiles(tiled2x2, tiled4x1, oneTile);
sourceImages = new RenderedImage[] {tiled2x2, tiled4x1, oneTile};
- final RenderedImage result = BandAggregateImage.create(sourceImages, null, null, allowSharing);
+ final RenderedImage result = BandAggregateImage.create(sourceImages, null, null, allowSharing, false);
assertNotNull(result);
assertEquals(minX, result.getMinX());
assertEquals(minY, result.getMinY());
@@ -350,7 +350,25 @@ public final class BandAggregateImageTest extends TestCase {
*/
@Test
@DependsOnMethod("testImagesUsingSameExtentButDifferentTileSizes")
- public void testImagesUsingDifferentExtentsAndDifferentSquaredTiling() {
+ public void testImagesUsingDifferentExtentsAndDifferentTiling() {
+ testHeterogeneous(false);
+ }
+
+ /**
+ * Tests {@link BandAggregateImage#prefetch(Rectangle)}.
+ */
+ @Test
+ @DependsOnMethod("testImagesUsingDifferentExtentsAndDifferentTiling")
+ public void testPrefetch() {
+ testHeterogeneous(true);
+ }
+
+ /**
+ * Implementation of test methods using tiles of different extents and different tile matrices.
+ *
+ * @param prefetch whether to test prefetch operation.
+ */
+ private void testHeterogeneous(final boolean prefetch) {
/*
* Tip: band number match image tile width. i.e:
*
@@ -366,7 +384,7 @@ public final class BandAggregateImageTest extends TestCase {
initializeAllTiles(untiled, tiled2x2, tiled4x4, tiled6x6);
sourceImages = new RenderedImage[] {untiled, tiled2x2, tiled4x4, tiled6x6};
- final RenderedImage result = BandAggregateImage.create(sourceImages, null, null, allowSharing);
+ RenderedImage result = BandAggregateImage.create(sourceImages, null, null, allowSharing, prefetch);
assertNotNull(result);
assertEquals(4, result.getMinX());
assertEquals(2, result.getMinY());
@@ -380,6 +398,9 @@ public final class BandAggregateImageTest extends TestCase {
assertEquals(2, result.getNumYTiles());
assertEquals(6, result.getSampleModel().getNumBands());
+ if (prefetch) {
+ result = new ImageProcessor().prefetch(result, new Rectangle(4, 2, 8, 4));
+ }
final Raster raster = result.getData();
assertEquals(new Rectangle(4, 2, 8, 4), raster.getBounds());
assertArrayEquals(
@@ -392,7 +413,9 @@ public final class BandAggregateImageTest extends TestCase {
},
raster.getPixels(4, 2, 8, 4, (int[]) null)
);
- verifySharing(result, false, allowSharing, true, false, false, false);
+ if (!prefetch) {
+ verifySharing(result, false, allowSharing, true, false, false, false);
+ }
}
/**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/BandAggregateGridResource.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/BandAggregateGridResource.java
index adb90fecb4..52e0f8b141 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/BandAggregateGridResource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/BandAggregateGridResource.java
@@ -26,7 +26,7 @@ import org.apache.sis.coverage.grid.GridCoverage;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.GridCoverageProcessor;
import org.apache.sis.coverage.grid.IllegalGridGeometryException;
-import org.apache.sis.internal.coverage.MultiSourcesArgument;
+import org.apache.sis.internal.coverage.MultiSourceArgument;
import org.apache.sis.internal.coverage.RangeArgument;
import org.apache.sis.storage.Resource;
import org.apache.sis.storage.GridCoverageResource;
@@ -163,7 +163,7 @@ public class BandAggregateGridResource extends AbstractGridCoverageResource {
{
super(parent);
try {
- final var aggregate = new MultiSourcesArgument<GridCoverageResource>(sources, bandsPerSource);
+ final var aggregate = new MultiSourceArgument<GridCoverageResource>(sources, bandsPerSource);
aggregate.validate(BandAggregateGridResource::range);
this.name = name;
this.sources = aggregate.sources();