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 <martin.desruisse...@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();