You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by am...@apache.org on 2023/02/15 17:30:14 UTC
[sis] 01/04: feat(Feature): allow user to override output color model for band aggregation
This is an automated email from the ASF dual-hosted git repository.
amanin pushed a commit to branch feat/resource-processor
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 17a4412ed745359710c89121ace77e9c7f9a2100
Author: Alexis Manin <al...@geomatys.com>
AuthorDate: Mon Dec 5 10:20:10 2022 +0100
feat(Feature): allow user to override output color model for band aggregation
Also, add a fallback strategy to guess a color model if user has not provided any.
---
.../org/apache/sis/image/BandAggregateImage.java | 65 ++++++++++++++++++----
.../java/org/apache/sis/image/ImageProcessor.java | 16 ++++--
.../apache/sis/image/BandAggregateImageTest.java | 2 +-
3 files changed, 66 insertions(+), 17 deletions(-)
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 0177d90563..924fae4a6f 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
@@ -6,6 +6,7 @@ import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.image.BandedSampleModel;
import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
@@ -16,6 +17,7 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.IntStream;
+import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
import org.apache.sis.util.ArgumentChecks;
import static java.lang.Math.multiplyExact;
@@ -76,8 +78,8 @@ final class BandAggregateImage extends ComputedImage {
* FACTORY METHODS
*/
- static RenderedImage aggregateBands(RenderedImage[] sources, int[][] bandsToPreserve) {
- final ContextInformation info = parseAndValidateInput(sources, bandsToPreserve);
+ static RenderedImage aggregateBands(RenderedImage[] sources, int[][] bandsToPreserve, ColorModel userColorModel) {
+ final ContextInformation info = parseAndValidateInput(sources, bandsToPreserve, userColorModel);
return tryTileOptimizedStrategy(info)
.rightOr(reason
-> fallbackStrategy(info)
@@ -95,16 +97,17 @@ final class BandAggregateImage extends ComputedImage {
* Initial analysis of input images to aggregate. Note that this method aims to make source information more
* accessible and easy to use before further processing. It also try to detect incompatibilities early, to
* raise meaningful errors for users.
- *
+ * <p>
* Note: crunching data into a more dense/accessible shape aims to ease further analysis/optimisations. This should
* allow more lisible and less coupled code, to ease setup of strategies, readability and maintenance.
*
- * @param sources images to aggregate, in order.
- * @param bandsToPreserve Bands to use for each image, in order. Holds same contract as the {@link #aggregateBands(RenderedImage[], int[][]) factory method}.
+ * @param sources images to aggregate, in order.
+ * @param bandsToPreserve Bands to use for each image, in order. Holds same contract as the {@link #aggregateBands(RenderedImage[], int[][], ColorModel) factory method}.
+ * @param userColorModel
* @return Parsed information about data sources.
* @throws IllegalArgumentException If we detect an incompatibility in source images that make them impossible to merge.
*/
- private static ContextInformation parseAndValidateInput(RenderedImage[] sources, int[][] bandsToPreserve) throws IllegalArgumentException {
+ private static ContextInformation parseAndValidateInput(RenderedImage[] sources, int[][] bandsToPreserve, ColorModel userColorModel) throws IllegalArgumentException {
if (bandsToPreserve != null && sources.length > bandsToPreserve.length) throw new IllegalArgumentException("More band selections than source images are provided.");
if (sources.length < 2) throw new IllegalArgumentException("At least two images are required for band aggregation. For band selection on a single image, please use dedicated utility");
@@ -153,7 +156,7 @@ final class BandAggregateImage extends ComputedImage {
.filter(it -> !it.isEmpty())
.orElseThrow(() -> new IllegalArgumentException("source images do not intersect."));
- return new ContextInformation(commonDataType, numBands, minTileWidthIdx, minTileHeightIdx, domains, intersection, sourcesWithBands);
+ return new ContextInformation(commonDataType, numBands, minTileWidthIdx, minTileHeightIdx, domains, intersection, sourcesWithBands, userColorModel);
}
private static int validateAndCountBands(int[] bandSelection, SampleModel model) {
@@ -203,11 +206,50 @@ final class BandAggregateImage extends ComputedImage {
final SampleModel tileModel = new BandedSampleModel(context.commonDataType, tileWidth, tileHeight, context.outputBandNumber);
Rectangle tileDisposition = new Rectangle(minTileX, minTileY, pixelDomain.width / tileWidth, pixelDomain.height / tileHeight);
- return Either.right(new Specification(Collections.unmodifiableList(Arrays.asList(preparedSources)), createColorModel(context), tileModel, pixelDomain, tileDisposition, new TileCopy()));
+ ColorModel outColorModel = context.userColorModel;
+ if (outColorModel == null) outColorModel = createColorModel(context);
+ else if (!context.userColorModel.isCompatibleSampleModel(tileModel)) {
+ throw new IllegalArgumentException("User color model is not compatible with band aggregation sample model. Please provide a banded color model.");
+ }
+
+ return Either.right(new Specification(Collections.unmodifiableList(Arrays.asList(preparedSources)), outColorModel, tileModel, pixelDomain, tileDisposition, new TileCopy()));
}
+ /**
+ * Approximate guess of the output color model:
+ * <ol>
+ * <li>
+ * If aggregation result is 3 or 4 bands, and data type is byte or short, we create a RGB color model.
+ * If there's 4 bands, an RGBA color model is defined.
+ * </li>
+ * <li>Otherwise, if the first image is already single banded, we return directly its color model (if non null)</li>
+ * <li>As a last resort, a greyscale color model is made, that try to "guess" value range from the data-type.</li>
+ * </ol>
+ */
private static ColorModel createColorModel(ContextInformation context) {
- return null; // TODO
+ if (context.outputBandNumber == 3 || context.outputBandNumber == 4) {
+ switch (context.commonDataType) {
+ case DataBuffer.TYPE_BYTE:
+ case DataBuffer.TYPE_SHORT:
+ return ColorModelFactory.createRGB(context.commonDataType * Byte.SIZE, false, context.outputBandNumber == 4);
+ }
+ }
+
+ final SourceSelection first = context.sources.get(0);
+ if (first.image.getSampleModel().getNumBands() == 1 && first.image.getColorModel() != null) {
+ return first.image.getColorModel();
+ }
+
+ final double vmin, vmax;
+ switch (context.commonDataType) {
+ case DataBuffer.TYPE_BYTE: vmin = 0 ; vmax = 255 ; break;
+ case DataBuffer.TYPE_SHORT: vmin = Short.MIN_VALUE ; vmax = Short.MAX_VALUE ; break;
+ case DataBuffer.TYPE_USHORT: vmin = 0 ; vmax = 65535 ; break;
+ case DataBuffer.TYPE_INT: vmin = 0 ; vmax = Integer.MAX_VALUE ; break;
+ default: vmin = 0.0 ; vmax = 1.0;
+ }
+
+ return ColorModelFactory.createGrayScale(context.commonDataType, 1, 0, vmin, vmax);
}
private static Either<String, Specification> fallbackStrategy(ContextInformation info) {
@@ -260,7 +302,9 @@ final class BandAggregateImage extends ComputedImage {
final List<SourceSelection> sources;
- public ContextInformation(int commonDataType, int outputBandNumber, int minTileWidthIndex, int minTileHeightIndex, List<Rectangle> sourcePxDomains, Rectangle intersection, List<SourceSelection> sources) {
+ final ColorModel userColorModel;
+
+ public ContextInformation(int commonDataType, int outputBandNumber, int minTileWidthIndex, int minTileHeightIndex, List<Rectangle> sourcePxDomains, Rectangle intersection, List<SourceSelection> sources, ColorModel userColorModel) {
this.commonDataType = commonDataType;
this.outputBandNumber = outputBandNumber;
this.minTileWidthIndex = minTileWidthIndex;
@@ -268,6 +312,7 @@ final class BandAggregateImage extends ComputedImage {
this.sourcePxDomains = Collections.unmodifiableList(new ArrayList<>(sourcePxDomains));
this.intersection = intersection;
this.sources = Collections.unmodifiableList(new ArrayList<>(sources));
+ this.userColorModel = userColorModel;
}
}
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 27a3bbb6a0..1c00c78bfd 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
@@ -1214,15 +1214,15 @@ public class ImageProcessor implements Cloneable {
}
/**
- * Commodity method for {@link #aggregateBands(List, List)}. Calling it is equivalent to:
+ * Commodity method for {@link #aggregateBands(List, List, ColorModel)}. Calling it is equivalent to:
*
- * {@code aggregateBands(Arrays.asList(sources), null);}
+ * {@code aggregateBands(Arrays.asList(sources), null, null);}
* @param sources images whose bands must be aggregated, in order. At least two images must be provided.
*
- * @see #aggregateBands(List, List)
+ * @see #aggregateBands(List, List, ColorModel)
*/
public RenderedImage aggregateBands(RenderedImage... sources) {
- return aggregateBands(Arrays.asList(sources), null);
+ return aggregateBands(Arrays.asList(sources), null, null);
}
/**
@@ -1231,17 +1231,21 @@ public class ImageProcessor implements Cloneable {
* @param bandsToSelectPerSource Bands to select for each source image, in order.
* If null or empty, we assume that all bands of all images must be selected.
* Any null or empty item means that all bands of the respective source image must be preserved.
+ * @param userColorModel Optional. The color model to apply on output image.
+ * If null, an approximate color model will be inferred using output number of bands and sample data type.
+ * There's no guarantee about the output color model, but it will not be null,
+ * and might be RGB or grey scale.
* @return A computed image whose bands are the bands of provided images, in order.
*/
- public RenderedImage aggregateBands(List<RenderedImage> sources, List<int[]> bandsToSelectPerSource) {
+ public RenderedImage aggregateBands(List<RenderedImage> sources, List<int[]> bandsToSelectPerSource, ColorModel userColorModel) {
RenderedImage[] sourceArray = sources.toArray(new RenderedImage[sources.size()]);
int[][] bandSelection = bandsToSelectPerSource == null || bandsToSelectPerSource.isEmpty()
? null
: bandsToSelectPerSource.stream()
.map(it -> it == null ? null : it.clone())
.toArray(int[][]::new);
- return BandAggregateImage.aggregateBands(sourceArray, bandSelection);
+ return BandAggregateImage.aggregateBands(sourceArray, bandSelection, userColorModel);
}
/**
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 3807345951..868113a33d 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
@@ -154,7 +154,7 @@ public class BandAggregateImageTest extends TestCase {
);
// Repeat the test with a custom band selection.
- result = processor.aggregateBands(Arrays.asList(im1, im2, im1), Arrays.asList(null, new int[] { 1 }, new int[] { 0 }));
+ result = processor.aggregateBands(Arrays.asList(im1, im2, im1), Arrays.asList(null, new int[] { 1 }, new int[] { 0 }), null);
assertNotNull(result);
assertArrayEquals(new int[] { 7, 7, 6, 9, 3, 3, 1, 2 }, new int[] { result.getMinX(), result.getMinY(), result.getWidth(), result.getHeight(), result.getTileWidth(), result.getTileHeight(), result.getMinTileX(), result.getMinTileY()});
raster = result.getData();