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 2022/11/28 08:36:28 UTC
[sis] 01/01: Resource : add ResourceProcessor with converted samples operation
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 cbf9f26ab9ac8170759dc1b7f5f5fc81a7489453
Author: jsorel <jo...@geomatys.com>
AuthorDate: Tue Sep 13 14:35:19 2022 +0200
Resource : add ResourceProcessor with converted samples operation
---
.../storage/ConvertedCoverageResource.java | 166 +++++++++++++++++++++
.../org/apache/sis/storage/ResourceProcessor.java | 69 +++++++++
.../storage/ConvertedCoverageResourceTest.java | 74 +++++++++
.../apache/sis/test/suite/StorageTestSuite.java | 1 +
4 files changed, 310 insertions(+)
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ConvertedCoverageResource.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ConvertedCoverageResource.java
new file mode 100644
index 0000000000..1c80113270
--- /dev/null
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ConvertedCoverageResource.java
@@ -0,0 +1,166 @@
+/*
+ * 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.internal.storage;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridCoverageProcessor;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.storage.AbstractGridCoverageResource;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.Query;
+import org.apache.sis.storage.RasterLoadingStrategy;
+import org.apache.sis.storage.UnsupportedQueryException;
+import org.apache.sis.util.ArgumentChecks;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.operation.MathTransform1D;
+import org.opengis.util.GenericName;
+
+/**
+ * Create a coverage resource with sample values converted by the given functions.
+ * The number of sample dimensions in the returned coverage is the length of the {@code converters} array,
+ * which must be greater than 0 and not greater than the number of sample dimensions in the source coverage.
+ * If the {@code converters} array length is less than the number of source sample dimensions,
+ * then all sample dimensions at index ≥ {@code converters.length} will be ignored.
+ *
+ * <h2>Sample dimensions customization</h2>
+ * By default, this method creates new sample dimensions with the same names and categories than in the
+ * previous coverage, but with {@linkplain org.apache.sis.coverage.Category#getSampleRange() sample ranges}
+ * converted using the given converters and with {@linkplain SampleDimension#getUnits() units of measurement}
+ * omitted. This behavior can be modified by specifying a non-null {@code sampleDimensionModifier} function.
+ * If non-null, that function will be invoked with, as input, a pre-configured sample dimension builder.
+ * The {@code sampleDimensionModifier} function can {@linkplain SampleDimension.Builder#setName(CharSequence)
+ * change the sample dimension name} or {@linkplain SampleDimension.Builder#categories() rebuild the categories}.
+ *
+ * <h2>Result relationship with source</h2>
+ * If the source coverage is backed by a {@link java.awt.image.WritableRenderedImage},
+ * then changes in the source coverage are reflected in the returned coverage and conversely.
+ *
+ * @see GridCoverageProcessor#convert(org.apache.sis.coverage.grid.GridCoverage, org.opengis.referencing.operation.MathTransform1D[], java.util.function.Function)
+ * @see ImageProcessor#convert(RenderedImage, NumberRange<?>[], MathTransform1D[], DataType, ColorModel)
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class ConvertedCoverageResource extends AbstractGridCoverageResource {
+
+ private final GridCoverageResource source;
+ private final MathTransform1D[] converters;
+ private final Function<SampleDimension.Builder, SampleDimension> sampleDimensionModifier;
+ private List<SampleDimension> sampleDimensions;
+
+ /**
+ *
+ * @param source the coverage resource for which to convert sample values.
+ * @param converters the transfer functions to apply on each sample dimension of the source coverage.
+ * @param sampleDimensionModifier a callback for modifying the {@link SampleDimension.Builder} default
+ * configuration for each sample dimension of the target coverage, or {@code null} if none.
+ */
+ public ConvertedCoverageResource(GridCoverageResource source, MathTransform1D[] converters,
+ Function<SampleDimension.Builder, SampleDimension> sampleDimensionModifier) {
+ super(null, false);
+ ArgumentChecks.ensureNonNull("base", source);
+ ArgumentChecks.ensureNonNull("converters", converters);
+ this.source = source;
+ this.converters = converters;
+ this.sampleDimensionModifier = sampleDimensionModifier;
+ }
+
+ @Override
+ public GridGeometry getGridGeometry() throws DataStoreException {
+ return source.getGridGeometry();
+ }
+
+ @Override
+ public synchronized List<SampleDimension> getSampleDimensions() throws DataStoreException {
+ if (sampleDimensions == null) {
+ sampleDimensions = new ArrayList<>(source.getSampleDimensions());
+ if (this.sampleDimensionModifier != null) {
+ for (int i = 0; i < converters.length; i++) {
+ final SampleDimension.Builder builder = new SampleDimension.Builder();
+ final SampleDimension band = sampleDimensions.get(i);
+ final MathTransform1D converter = converters[i];
+ band.getBackground().ifPresent(builder::setBackground);
+ band.getCategories().forEach((category) -> {
+ if (category.isQuantitative()) {
+ // Unit is assumed different as a result of conversion.
+ builder.addQuantitative(category.getName(), category.getSampleRange(), converter, null);
+ } else {
+ builder.addQualitative(category.getName(), category.getSampleRange());
+ }
+ });
+ builder.setName(band.getName());
+ sampleDimensions.set(i, sampleDimensionModifier.apply(builder));
+ }
+ }
+ sampleDimensions = Collections.unmodifiableList(sampleDimensions);
+ }
+ return sampleDimensions;
+ }
+
+ @Override
+ public List<double[]> getResolutions() throws DataStoreException {
+ return source.getResolutions();
+ }
+
+ @Override
+ public GridCoverageResource subset(Query query) throws UnsupportedQueryException, DataStoreException {
+ final GridCoverageResource subset = source.subset(query);
+ return new ConvertedCoverageResource(subset, converters, sampleDimensionModifier);
+ }
+
+ @Override
+ public GridCoverage read(GridGeometry domain, int... range) throws DataStoreException {
+ final GridCoverage coverage = source.read(domain, range);
+ final MathTransform1D[] trs;
+ if (range != null) {
+ trs = new MathTransform1D[range.length];
+ for (int i = 0; i < trs.length; i++) {
+ trs[i] = converters[range[i]];
+ }
+ } else {
+ trs = converters.clone();
+ }
+ return new GridCoverageProcessor().convert(coverage, trs, sampleDimensionModifier);
+ }
+
+ @Override
+ public RasterLoadingStrategy getLoadingStrategy() throws DataStoreException {
+ return source.getLoadingStrategy();
+ }
+
+ @Override
+ public boolean setLoadingStrategy(RasterLoadingStrategy strategy) throws DataStoreException {
+ return source.setLoadingStrategy(strategy);
+ }
+
+ @Override
+ public Optional<Envelope> getEnvelope() throws DataStoreException {
+ return source.getEnvelope();
+ }
+
+ @Override
+ public Optional<GenericName> getIdentifier() throws DataStoreException {
+ return source.getIdentifier();
+ }
+
+}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/ResourceProcessor.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/ResourceProcessor.java
new file mode 100644
index 0000000000..f8228daadf
--- /dev/null
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/ResourceProcessor.java
@@ -0,0 +1,69 @@
+/*
+ * 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.storage;
+
+import java.util.function.Function;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.internal.storage.ConvertedCoverageResource;
+import org.opengis.referencing.operation.MathTransform1D;
+
+/**
+ * A predefined set of operations on resources as convenience methods.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @version 1.3
+ * @since 1.3
+ * @module
+ */
+public class ResourceProcessor implements Cloneable {
+
+ /**
+ * Creates a new processor with default configuration.
+ */
+ public ResourceProcessor(){
+
+ }
+
+ /**
+ * Returns a coverage resource with sample values converted by the given functions.
+ * The number of sample dimensions in the returned coverage is the length of the {@code converters} array,
+ * which must be greater than 0 and not greater than the number of sample dimensions in the source coverage.
+ * If the {@code converters} array length is less than the number of source sample dimensions,
+ * then all sample dimensions at index ≥ {@code converters.length} will be ignored.
+ *
+ * <h4>Sample dimensions customization</h4>
+ * By default, this method creates new sample dimensions with the same names and categories than in the
+ * previous coverage, but with {@linkplain org.apache.sis.coverage.Category#getSampleRange() sample ranges}
+ * converted using the given converters and with {@linkplain SampleDimension#getUnits() units of measurement}
+ * omitted. This behavior can be modified by specifying a non-null {@code sampleDimensionModifier} function.
+ * If non-null, that function will be invoked with, as input, a pre-configured sample dimension builder.
+ * The {@code sampleDimensionModifier} function can {@linkplain SampleDimension.Builder#setName(CharSequence)
+ * change the sample dimension name} or {@linkplain SampleDimension.Builder#categories() rebuild the categories}.
+ *
+ * <h4>Result relationship with source</h4>
+ * If the source coverage is backed by a {@link java.awt.image.WritableRenderedImage},
+ * then changes in the source coverage are reflected in the returned coverage and conversely.
+ *
+ * @see GridCoverageProcessor#convert(org.apache.sis.coverage.grid.GridCoverage, org.opengis.referencing.operation.MathTransform1D[], java.util.function.Function)
+ * @see ImageProcessor#convert(RenderedImage, NumberRange<?>[], MathTransform1D[], DataType, ColorModel)
+ */
+ public GridCoverageResource convert(final GridCoverageResource source, MathTransform1D[] converters,
+ Function<SampleDimension.Builder, SampleDimension> sampleDimensionModifier)
+ {
+ return new ConvertedCoverageResource(source, converters, sampleDimensionModifier);
+ }
+}
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ConvertedCoverageResourceTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ConvertedCoverageResourceTest.java
new file mode 100644
index 0000000000..cd42680f52
--- /dev/null
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ConvertedCoverageResourceTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.internal.storage;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import org.apache.sis.coverage.grid.GridCoverage2D;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.coverage.grid.GridOrientation;
+import org.apache.sis.image.PixelIterator;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.test.TestCase;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.opengis.referencing.operation.MathTransform1D;
+
+/**
+ * Tests {@link ConvertedCoverageResource}.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @version 1.3
+ * @since 1.3
+ * @module
+ */
+public final class ConvertedCoverageResourceTest extends TestCase {
+
+
+ /**
+ * Tests {@link ConvertedCoverageResource}.
+ */
+ @Test
+ public void testConvert() throws DataStoreException {
+
+ //source coverage
+ final BufferedImage data = new BufferedImage(360, 180, BufferedImage.TYPE_BYTE_GRAY);
+ final GridGeometry grid = new GridGeometry(new GridExtent(360,180), CRS.getDomainOfValidity(CommonCRS.WGS84.normalizedGeographic()), GridOrientation.HOMOTHETY);
+ final GridCoverage2D coverage = new GridCoverage2D(grid, null, data);
+ final GridCoverageResource source = new MemoryGridResource(null, coverage);
+
+ //converted coverage
+ final MathTransform1D converter = (MathTransform1D) MathTransforms.linear(2, 10);
+ final ConvertedCoverageResource converted = new ConvertedCoverageResource(source, new MathTransform1D[]{converter}, null);
+
+ //ensure structure is preserved
+ assertEquals(source.getGridGeometry(), converted.getGridGeometry());
+ assertEquals(source.getSampleDimensions(), converted.getSampleDimensions());
+ assertEquals(source.getIdentifier(), converted.getIdentifier());
+
+ //ensure values are modified
+ final RenderedImage convertedImage = converted.read(grid, 0).render(grid.getExtent());
+ final PixelIterator ite = PixelIterator.create(convertedImage);
+ ite.moveTo(0, 0);
+ assertEquals(10, ite.getSample(0));
+ }
+}
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java b/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
index ad3ab0fa51..af2d2b5554 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
@@ -43,6 +43,7 @@ import org.junit.BeforeClass;
org.apache.sis.internal.storage.MetadataBuilderTest.class,
org.apache.sis.internal.storage.RangeArgumentTest.class,
org.apache.sis.internal.storage.MemoryGridResourceTest.class,
+ org.apache.sis.internal.storage.ConvertedCoverageResourceTest.class,
org.apache.sis.storage.FeatureNamingTest.class,
org.apache.sis.storage.ProbeResultTest.class,
org.apache.sis.storage.StorageConnectorTest.class,