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,