sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: GridCoverage.forConvertedValues(true) sometime created an image that can not store NaN values.
Date Fri, 19 Jun 2020 10:39:34 GMT
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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 4ef3ef2  GridCoverage.forConvertedValues(true) sometime created an image that can
not store NaN values.
4ef3ef2 is described below

commit 4ef3ef2e69ec83496e9b35ce69875a6f89e668e5
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Fri Jun 19 12:26:57 2020 +0200

    GridCoverage.forConvertedValues(true) sometime created an image that can not store NaN
values.
    
    https://issues.apache.org/jira/browse/SIS-496
---
 .../java/org/apache/sis/coverage/CategoryList.java | 14 +++-
 .../org/apache/sis/coverage/SampleDimension.java   | 19 +++++
 .../sis/coverage/grid/BufferedGridCoverage.java    |  9 +++
 .../sis/coverage/grid/ConvertedGridCoverage.java   | 24 ++++++-
 .../org/apache/sis/coverage/grid/GridCoverage.java |  9 +++
 .../apache/sis/coverage/grid/GridCoverage2D.java   | 11 ++-
 .../sis/coverage/grid/ResampledGridCoverage.java   |  9 +++
 .../coverage/grid/ConvertedGridCoverageTest.java   | 82 ++++++++++++++++++++++
 .../java/org/apache/sis/test/FeatureAssert.java    | 28 ++++++++
 .../apache/sis/test/suite/FeatureTestSuite.java    |  1 +
 .../org/apache/sis/portrayal/CanvasContext.java    |  6 +-
 11 files changed, 202 insertions(+), 10 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/CategoryList.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/CategoryList.java
index 7a7ae1f..fb14bcc 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/CategoryList.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/CategoryList.java
@@ -335,7 +335,7 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
              */
             if (isSampleToUnit) {
                 final int n = converse.minimums.length;
-                if (n != 0 && Double.isNaN(converse.minimums[n - 1])) {
+                if (n != 0 && isNaN(converse.minimums[n - 1])) {
                     fallback = 0;
                     return;
                 }
@@ -394,6 +394,14 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
     }
 
     /**
+     * Returns {@code true} if the category list contains at least one NaN value.
+     */
+    final boolean allowsNaN() {
+        final int n = minimums.length;
+        return (n != 0) && isNaN(minimums[n-1]);
+    }
+
+    /**
      * Returns the <cite>transfer function</cite> from sample values to real
values, including conversion
      * of "no data" values to NaNs. Callers shall ensure that there is at least one quantitative
category
      * before to invoke this method.
@@ -502,7 +510,7 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
         if (MathFunctions.isPositiveZero(fallback)) {
             return value;
         }
-        if (Double.isNaN(fallback)) {
+        if (isNaN(fallback)) {
             throw new TransformException(formatNoCategory(value));
         }
         return fallback;
@@ -513,7 +521,7 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
      */
     private static String formatNoCategory(final double value) {
         return Resources.format(Resources.Keys.NoCategoryForValue_1,
-                Double.isNaN(value) ? "NaN #" + MathFunctions.toNanOrdinal((float) value)
: value);
+                isNaN(value) ? "NaN #" + MathFunctions.toNanOrdinal((float) value) : value);
     }
 
     /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
index 9bf91d6..34925c3 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
@@ -238,6 +238,8 @@ public class SampleDimension implements Serializable {
      * @return the values to indicate no data values for this sample dimension, or an empty
set if none.
      * @throws IllegalStateException if this method can not expand the range of no data values,
for example
      *         because some ranges contain an infinite amount of values.
+     *
+     * @see #allowsNaN()
      */
     public Set<Number> getNoDataValues() {
         if (converse != null) {             // Null if SampleDimension does not contain at
least one quantitative category.
@@ -396,6 +398,23 @@ public class SampleDimension implements Serializable {
     }
 
     /**
+     * Returns {@code true} if some sample values can be {@link Float#NaN NaN} values.
+     * It may be the case for {@linkplain #forConvertedValues(boolean) converted values},
+     * but not necessarily (because a coverage does not necessarily allow missing values).
+     * If {@code true}, then the NaN values should be listed by {@link #getNoDataValues()}.
+     *
+     * @return whether some values in this sample dimension can be {@link Float#NaN NaN}.
+     *
+     * @see #getNoDataValues()
+     * @see org.apache.sis.math.MathFunctions#toNanFloat(int)
+     *
+     * @since 1.1
+     */
+    public boolean allowsNaN() {
+        return categories.allowsNaN();
+    }
+
+    /**
      * Returns a sample dimension that describes real values or sample values, depending
if {@code converted} is {@code true}
      * or {@code false} respectively.  If there is no {@linkplain #getTransferFunction()
transfer function}, then this method
      * returns {@code this}.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
index 15392d3..8cb915a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BufferedGridCoverage.java
@@ -177,6 +177,15 @@ public class BufferedGridCoverage extends GridCoverage {
     }
 
     /**
+     * Returns the {@link java.awt.image.DataBuffer} constant
+     * identifying the primitive type used for storing sample values.
+     */
+    @Override
+    final int getDataType() {
+        return data.getDataType();
+    }
+
+    /**
      * Creates a new function for computing or interpolating sample values at given locations.
      *
      * <h4>Multi-threading</h4>
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
index 5027b58..d1c0595 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ConvertedGridCoverage.java
@@ -94,7 +94,7 @@ final class ConvertedGridCoverage extends GridCoverage {
         this.source      = source;
         this.converters  = converters;
         this.isConverted = isConverted;
-        this.dataType    = getDataType(range, isConverted);
+        this.dataType    = getDataType(range, isConverted, source);
     }
 
     /**
@@ -157,10 +157,12 @@ final class ConvertedGridCoverage extends GridCoverage {
      *
      * @param  targets    the sample dimensions for which to get the data type.
      * @param  converted  whether the image will hold converted or packed values.
+     * @param  source     if the type can not be determined, coverage from which to inherit
the type as a fallback.
      * @return the {@link DataBuffer} type.
      */
-    public static int getDataType(final List<SampleDimension> targets, final boolean
converted) {
+    static int getDataType(final List<SampleDimension> targets, final boolean converted,
final GridCoverage source) {
         NumberRange<?> union = null;
+        boolean allowsNaN = false;
         for (final SampleDimension dimension : targets) {
             final Optional<NumberRange<?>> c = dimension.getSampleRange();
             if (c.isPresent()) {
@@ -171,8 +173,24 @@ final class ConvertedGridCoverage extends GridCoverage {
                     union = union.unionAny(range);
                 }
             }
+            if (!allowsNaN) allowsNaN = dimension.allowsNaN();
         }
-        return RasterFactory.getDataType(union, converted);
+        int type = RasterFactory.getDataType(union, converted);
+        if (allowsNaN && type >= DataBuffer.TYPE_BYTE && type < DataBuffer.TYPE_FLOAT)
{
+            type = (type < DataBuffer.TYPE_INT) ? DataBuffer.TYPE_FLOAT : DataBuffer.TYPE_DOUBLE;
+        }
+        if (type == DataBuffer.TYPE_UNDEFINED) {
+            type = source.getDataType();
+        }
+        return type;
+    }
+
+    /**
+     * Returns the {@link DataBuffer} constant identifying the primitive type used for storing
sample values.
+     */
+    @Override
+    final int getDataType() {
+        return dataType;
     }
 
     /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index 131036d..c5bdc33 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@ -21,6 +21,7 @@ import java.util.Collection;
 import java.util.Locale;
 import java.util.Optional;
 import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
 import java.awt.image.RenderedImage;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
@@ -171,6 +172,14 @@ public abstract class GridCoverage {
     }
 
     /**
+     * Returns the {@link DataBuffer} constant identifying the primitive type used for storing
sample values.
+     * If the type is unknown, returns {@link DataBuffer#TYPE_UNDEFINED}.
+     */
+    int getDataType() {
+        return DataBuffer.TYPE_UNDEFINED;
+    }
+
+    /**
      * Creates a color model for the expected range of sample values.
      *
      * @param  visibleBand  the band to be made visible (usually 0). All other bands (if
any) will be ignored.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
index 27785cd..84960a1 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -150,7 +150,7 @@ public class GridCoverage2D extends GridCoverage {
                            final MathTransform1D[] converters, final boolean isConverted)
     {
         super(source.gridGeometry, range);
-        final int dataType = ConvertedGridCoverage.getDataType(range, isConverted);
+        final int dataType = ConvertedGridCoverage.getDataType(range, isConverted, source);
         final ColorModel colorModel = createColorModel(Math.max(0, ImageUtilities.getVisibleBand(source.data)),
dataType);
         data           = BandedSampleConverter.create(source.data, null, dataType, colorModel,
getRanges(), converters);
         gridToImageX   = source.gridToImageX;
@@ -444,6 +444,15 @@ public class GridCoverage2D extends GridCoverage {
     }
 
     /**
+     * Returns the {@link java.awt.image.DataBuffer} constant
+     * identifying the primitive type used for storing sample values.
+     */
+    @Override
+    final int getDataType() {
+        return ImageUtilities.getDataType(data);
+    }
+
+    /**
      * Returns the two-dimensional part of this grid geometry.
      * If the {@linkplain #getGridGeometry() complete geometry} is already two-dimensional,
      * then this method returns the same geometry. Otherwise it returns a geometry for the
two first
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
index 22da1bc..e675237 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
@@ -529,6 +529,15 @@ final class ResampledGridCoverage extends GridCoverage {
     }
 
     /**
+     * Returns the {@link java.awt.image.DataBuffer} constant
+     * identifying the primitive type used for storing sample values.
+     */
+    @Override
+    final int getDataType() {
+        return source.getDataType();
+    }
+
+    /**
      * Delegates to the source coverage, which should transform the point itself if needed.
      */
     @Override
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ConvertedGridCoverageTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ConvertedGridCoverageTest.java
new file mode 100644
index 0000000..455e310
--- /dev/null
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ConvertedGridCoverageTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.coverage.grid;
+
+import java.util.Collections;
+import java.awt.image.DataBuffer;
+import org.opengis.referencing.datum.PixelInCell;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+import org.apache.sis.math.MathFunctions;
+import org.apache.sis.measure.Units;
+import org.apache.sis.referencing.crs.HardCodedCRS;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.apache.sis.test.FeatureAssert.*;
+
+
+/**
+ * Tests {@link ConvertedGridCoverage}.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public final strictfp class ConvertedGridCoverageTest extends TestCase {
+    /**
+     * Tests forward conversion from packed values to "geophysics" values.
+     * Test includes a conversion of an integer value to {@link Float#NaN}.
+     */
+    @Test
+    public void testForward() {
+        /*
+         * A sample dimension with an identity transfer function
+         * except for value -1 which will be mapped to NaN.
+         */
+        final SampleDimension sd = new SampleDimension.Builder()
+                .addQualitative(null, -1)
+                .addQuantitative("data", 0, 10, 1, 0, Units.UNITY)
+                .setName("data")
+                .build();
+        /*
+         * Creates an image of 2 pixels on a single row with sample values (-1, 3).
+         * The "grid to CRS" transform does not matter for this test.
+         */
+        final GridGeometry grid = new GridGeometry(new GridExtent(2, 1), PixelInCell.CELL_CENTER,
+                new AffineTransform2D(1, 0, 0, 1, 1, 0), HardCodedCRS.WGS84);
+
+        final BufferedGridCoverage coverage = new BufferedGridCoverage(
+                grid, Collections.singleton(sd), DataBuffer.TYPE_SHORT);
+
+        coverage.data.setElem(0, -1);
+        coverage.data.setElem(1,  3);
+        /*
+         * Verify values before and after conversion.
+         */
+        assertValuesEqual(coverage.forConvertedValues(false).render(null), 0, new double[][]
{
+            {-1, 3}
+        });
+        final float nan = MathFunctions.toNanFloat(-1);
+        assertTrue(Float.isNaN(nan));
+        assertValuesEqual(coverage.forConvertedValues(true).render(null), 0, new double[][]
{
+            {nan, 3}
+        });
+    }
+}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/test/FeatureAssert.java b/core/sis-feature/src/test/java/org/apache/sis/test/FeatureAssert.java
index 1f9ba4f..3d0781c 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/test/FeatureAssert.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/FeatureAssert.java
@@ -79,6 +79,34 @@ public strictfp class FeatureAssert extends ReferencingAssert {
     }
 
     /**
+     * Verifies that sample values in the given image are equal to the expected floating
point values.
+     * NaN values are compared using {@link Double#doubleToRawLongBits(double)} (i.e. different
NaNs
+     * are <em>not</em> collapsed in a canonical NaN value).
+     *
+     * @param  image     the image to verify.
+     * @param  band      the band to verify.
+     * @param  expected  the expected sample values.
+     */
+    public static void assertValuesEqual(final RenderedImage image, final int band, final
double[][] expected) {
+        assertEquals("Height", expected.length, image.getHeight());
+        final PixelIterator it = PixelIterator.create(image);
+        for (int j=0; j<expected.length; j++) {
+            final double[] row = expected[j];
+            assertEquals("Width", row.length, image.getWidth());
+            for (int i=0; i<row.length; i++) {
+                assertTrue(it.next());
+                final double a = it.getSampleDouble(band);
+                final double e = row[i];
+                if (Double.doubleToRawLongBits(a) != Double.doubleToRawLongBits(e)) {
+                    final Point p = it.getPosition();
+                    fail(mismatchedSampleValue(p.x, p.y, i, j, band, e, a));
+                }
+            }
+        }
+        assertFalse(it.next());
+    }
+
+    /**
      * Verifies that sample values in the given raster are equal to the expected integer
values.
      *
      * @param  raster    the raster to verify.
diff --git a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
index 21a1ac4..bc62e53 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
@@ -99,6 +99,7 @@ import org.junit.runners.Suite;
     org.apache.sis.coverage.grid.GridCoverage2DTest.class,
     org.apache.sis.coverage.grid.BufferedGridCoverageTest.class,
     org.apache.sis.coverage.grid.GridCoverageBuilderTest.class,
+    org.apache.sis.coverage.grid.ConvertedGridCoverageTest.class,
     org.apache.sis.coverage.grid.ResampledGridCoverageTest.class,
     org.apache.sis.internal.coverage.j2d.BandedSampleConverterTest.class,
 
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/CanvasContext.java
b/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/CanvasContext.java
index 42fbd22..3747cbb 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/CanvasContext.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/portrayal/CanvasContext.java
@@ -52,7 +52,7 @@ final class CanvasContext extends CoordinateOperationContext {
      * Desired resolution in display units (usually pixels). This is used for avoiding
      * the cost of transformations having too much accuracy for the current zoom level.
      *
-     * @see Canvas#findTransform(CoordinateReferenceSystem, CoordinateReferenceSystem)
+     * @see Canvas#findTransform(CoordinateReferenceSystem, CoordinateReferenceSystem, boolean)
      */
     private static final double DISPLAY_RESOLUTION = 1;
 
@@ -60,8 +60,8 @@ final class CanvasContext extends CoordinateOperationContext {
      * Transformation from {@linkplain Canvas#getObjectiveCRS() objective CRS} to a geographic
CRS, or {@code null} if
      * none can be found. The geographic CRS (operation target) has (longitude, latitude)
axes in degrees but the same
      * geodetic datum than the objective CRS, so the prime meridian is not necessarily Greenwich.
This is recomputed
-     * immediately after a change of {@link Canvas#objectiveCRS} or {@link Canvas#pointOfInterest}
because it will
-     * be needed anyway for {@link Canvas#findTransform(CoordinateReferenceSystem, CoordinateReferenceSystem)}.
+     * immediately after a change of {@link Canvas#objectiveCRS} or {@link Canvas#pointOfInterest}
because it will be
+     * needed anyway for {@link Canvas#findTransform(CoordinateReferenceSystem, CoordinateReferenceSystem,
boolean)}.
      *
      * @see Canvas#getGeographicArea()
      * @see Canvas#objectiveToGeographic(CoordinateReferenceSystem)


Mime
View raw message