You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/05/26 10:15:03 UTC

[sis] 02/02: When the GeoTIFF image is of floating point type, replace fill values by NaN at reading time. This is the same policy than with our netCDF reader.

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 77cdb0047c6203bdf01102109ee055c18046b60e
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri May 26 12:13:03 2023 +0200

    When the GeoTIFF image is of floating point type, replace fill values by NaN at reading time.
    This is the same policy than with our netCDF reader.
---
 .../org/apache/sis/coverage/SampleDimension.java   |  7 +++++
 .../sis/internal/coverage/SampleDimensions.java    |  6 ++--
 .../java/org/apache/sis/measure/NumberRange.java   |  4 +--
 .../sis/internal/geotiff/SchemaModifier.java       |  2 +-
 .../sis/storage/geotiff/CompressedSubset.java      |  4 +--
 .../org/apache/sis/storage/geotiff/DataCube.java   | 18 ++++++++++++
 .../org/apache/sis/storage/geotiff/DataSubset.java | 33 +++++++++++++++++++++-
 .../sis/storage/geotiff/ImageFileDirectory.java    | 13 +++++++--
 8 files changed, 77 insertions(+), 10 deletions(-)

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 f0a24e8c9e..4ec24af099 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
@@ -1209,6 +1209,13 @@ public class SampleDimension implements Serializable {
                     toNaN.remove(c);
                     return c;
                 }
+
+                /** Removes all categories in the given range. */
+                @Override protected void removeRange(final int fromIndex, final int toIndex) {
+                    System.arraycopy(categories, toIndex, categories, fromIndex, count - toIndex);
+                    Arrays.fill(categories, toIndex, count, null);
+                    count -= (toIndex - fromIndex);
+                }
             };
         }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/SampleDimensions.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/SampleDimensions.java
index 9952fb69c8..c7658d01a3 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/SampleDimensions.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/SampleDimensions.java
@@ -155,8 +155,9 @@ public final class SampleDimensions extends Static {
             final SampleDimension band = bands[i];
             if (band != null) {
                 final List<Category> categories = band.getCategories();
-                final Number[] nodataValues = new Number[categories.size()];
-                for (int j = 0; j < nodataValues.length; j++) {
+                final int count = categories.size();
+                final Number[] nodataValues = new Number[count + 1];
+                for (int j = 0; j < count; j++) {
                     final Category category = categories.get(j);
                     if (!category.isQuantitative()) {
                         final NumberRange<?> range = category.getSampleRange();
@@ -171,6 +172,7 @@ public final class SampleDimensions extends Static {
                         nodataValues[j] = value;
                     }
                 }
+                nodataValues[count] = band.getBackground().orElse(null);
                 sampleFilters[i] = ImageProcessor.filterNodataValues(nodataValues);
             }
         }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java b/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
index 019eda568d..c4cc0a29a3 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
@@ -729,8 +729,8 @@ public class NumberRange<E extends Number & Comparable<? super E>> extends Range
      * This method converts {@code this} or the given argument to the widest numeric type,
      * then performs the same work than {@link #contains(Comparable)}.
      *
-     * @param  value  the value to check for inclusion in this range.
-     * @return {@code true} if the given value is included in this range.
+     * @param  value  the value to check for inclusion in this range, or {@code null}.
+     * @return {@code true} if the given value is non-null and included in this range.
      * @throws IllegalArgumentException if the given range cannot be converted to a valid type
      *         through widening conversion.
      */
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/SchemaModifier.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/SchemaModifier.java
index 40d751d177..0f0659c2a6 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/SchemaModifier.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/SchemaModifier.java
@@ -112,7 +112,7 @@ public interface SchemaModifier {
      * @todo if we move this key in public API in the future, then it would be a
      *       value in existing {@link org.apache.sis.storage.DataOptionKey} class.
      */
-    OptionKey<SchemaModifier> OPTION = new InternalOptionKey<SchemaModifier>("SCHEMA_MODIFIER", SchemaModifier.class);
+    OptionKey<SchemaModifier> OPTION = new InternalOptionKey<>("SCHEMA_MODIFIER", SchemaModifier.class);
 
     /**
      * The default instance which performs no modification.
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java
index 2639a4209f..fe926da63a 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java
@@ -35,7 +35,7 @@ import static java.lang.Math.multiplyFull;
  * Raster data obtained from a compressed GeoTIFF file in the domain requested by user.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   1.1
  */
 final class CompressedSubset extends DataSubset {
@@ -249,7 +249,7 @@ final class CompressedSubset extends DataSubset {
             fillRemainingRows(bank.flip());
             banks[b] = bank;
         }
-        return Raster.createWritableRaster(model, RasterFactory.wrap(dataType, banks), location);
+        return createWritableRaster(RasterFactory.wrap(dataType, banks), location);
     }
 
     /**
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
index fc2a2679f4..e8b09a1f50 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
@@ -160,6 +160,24 @@ abstract class DataCube extends TiledGridResource implements ResourceOnFileSyste
      */
     abstract Predictor getPredictor();
 
+    /**
+     * Returns the fill value used in image of floating point type before replacement by NaN.
+     * If no value is declared or if the image type is an integer type, returns {@code null}.
+     * If non-null, this value will be replaced by {@link Float#NaN} at reading time.
+     *
+     * <div class="note"><b>Rational:</b>
+     * the intend is to handle the image as if it was already converted to the units of measurement.
+     * Our netCDF reader does the same thing, and we want a consistent behavior of coverage readers.
+     * </div>
+     *
+     * If this method returns a non-null value, then {@link #getFillValue()} should return NaN.
+     *
+     * @return value to be replaced by NaN at reading time, or {@code null} if none.
+     *
+     * @see #getFillValue()
+     */
+    abstract Number getReplaceableFillValue();
+
     /**
      * Returns {@code true} if the image can be read with the {@link DataSubset} base class,
      * or {@code false} if the more sophisticated {@link CompressedSubset} sub-class is needed.
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
index 043a3df5d4..58192bc408 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
@@ -24,6 +24,8 @@ import java.io.IOException;
 import java.awt.Point;
 import java.awt.image.BandedSampleModel;
 import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferFloat;
+import java.awt.image.DataBufferDouble;
 import java.awt.image.Raster;
 import org.opengis.util.GenericName;
 import org.apache.sis.image.DataType;
@@ -41,6 +43,7 @@ import org.apache.sis.internal.storage.io.ChannelDataInput;
 import org.apache.sis.internal.geotiff.Resources;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.Localized;
+import org.apache.sis.util.ArraysExt;
 import org.apache.sis.math.Vector;
 
 import static java.lang.Math.addExact;
@@ -523,7 +526,7 @@ class DataSubset extends TiledGridCoverage implements Localized {
             banks[b] = bank;
         }
         final DataBuffer buffer = RasterFactory.wrap(type, banks);
-        return Raster.createWritableRaster(model, buffer, location);
+        return createWritableRaster(buffer, location);
     }
 
     /**
@@ -544,4 +547,32 @@ class DataSubset extends TiledGridCoverage implements Localized {
             }
         }
     }
+
+    /**
+     * Creates the raster with the given data buffer, which contains the pixels that have been read.
+     *
+     * @param  buffer    the sample values which have been read from the GeoTIFF tile.
+     * @param  location  pixel coordinates in the upper-left corner of the tile to return.
+     * @return raster with the given sample values.
+     */
+    final Raster createWritableRaster(final DataBuffer buffer, final Point location) {
+        final Number fill = source.getReplaceableFillValue();
+        if (fill != null) {
+            switch (buffer.getDataType()) {
+                case DataBuffer.TYPE_FLOAT: {
+                    for (float[] bank : ((DataBufferFloat) buffer).getBankData()) {
+                        ArraysExt.replace(bank, fill.floatValue(), Float.NaN);
+                    }
+                    break;
+                }
+                case DataBuffer.TYPE_DOUBLE: {
+                    for (double[] bank : ((DataBufferDouble) buffer).getBankData()) {
+                        ArraysExt.replace(bank, fill.doubleValue(), Double.NaN);
+                    }
+                    break;
+                }
+            }
+        }
+        return Raster.createWritableRaster(model, buffer, location);
+    }
 }
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index be107010df..0d255f0953 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -1691,6 +1691,15 @@ final class ImageFileDirectory extends DataCube {
         return colorModel;
     }
 
+    /**
+     * Returns the fill value to be replaced by NaN at reading time.
+     * This is possible only for images of floating point type.
+     */
+    @Override
+    Number getReplaceableFillValue() {
+        return (sampleFormat == FLOAT) ? noData : null;
+    }
+
     /**
      * Returns the value to use for filling empty spaces in the raster, or {@code null} if none,
      * not different than zero or not valid for the target data type.
@@ -1698,11 +1707,11 @@ final class ImageFileDirectory extends DataCube {
      */
     @Override
     protected Number getFillValue() {
-        return getFillValue(false);
+        return (sampleFormat != FLOAT) ? getFillValue(false) : Double.NaN;
     }
 
     /**
-     * Returns the value to use for filling empty spaces in the raster, or {@code null} if none,
+     * Returns the value to use for filling empty spaces in the raster, or {@code null} if none.
      * The exclusion of zero value is optional, controlled by the {@code acceptZero} argument.
      *
      * @param  acceptZero  whether to return a number for the zero value.