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 09ee4ee Make easier to build a sample dimension with a background value but no category. This is necessary for keeping RGB images as "for visualization only" images. 09ee4ee is described below commit 09ee4ee7d4cb84ecd52988d6a1bc64b1e213f638 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Thu Dec 30 16:51:28 2021 +0100 Make easier to build a sample dimension with a background value but no category. This is necessary for keeping RGB images as "for visualization only" images. --- .../org/apache/sis/coverage/SampleDimension.java | 75 ++++++++++++++-------- .../sis/internal/coverage/SampleDimensions.java | 41 ------------ .../sis/internal/coverage/j2d/Colorizer.java | 20 +++--- .../sis/internal/map/coverage/RenderingData.java | 2 +- .../java/org/apache/sis/storage/landsat/Band.java | 2 +- .../sis/internal/geotiff/SchemaModifier.java | 16 ++--- .../sis/storage/geotiff/ImageFileDirectory.java | 6 +- .../sis/internal/sql/postgis/RasterReader.java | 6 +- 8 files changed, 71 insertions(+), 97 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 819634f..3d0af60 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 @@ -77,7 +77,7 @@ import org.apache.sis.util.Debug; * * @author Martin Desruisseaux (IRD, Geomatys) * @author Alexis Manin (Geomatys) - * @version 1.1 + * @version 1.2 * * @see org.opengis.metadata.content.SampleDimension * @@ -591,6 +591,22 @@ public class SampleDimension implements Serializable { } /** + * Returns the name specified by the last call to a {@code setName(…)} method. + * If {@code setName(…)} has not been invoked or if {@link #clear()} has been invoked after, + * then this method returns {@code null}. In that case the {@link #build()} method will default + * to the name of the first quantitative category if present, or "Untitled" (localized) otherwise. + * + * @return the name explicitly set by last call to {@code setName(…)}, or {@code null} if none or cleared. + * + * @see SampleDimension#getName() + * + * @since 1.2 + */ + public GenericName getName() { + return dimensionName; + } + + /** * Sets an identification of the sample dimension. * This is the value to be returned by {@link SampleDimension#getName()}. * If this method is invoked more than once, then the last specified name prevails @@ -680,22 +696,35 @@ public class SampleDimension implements Serializable { } /** - * Returns the background value to use by default if none were explicitly defined. - * This method is invoked at {@linkplain #build() build} time - * if the {@link #setBackground(CharSequence, Number)} method has never been invoked - * since {@code Builder} construction or since the last call to {@link #clear()}. - * The background value returned by this method is not associated to any category. + * Returns the value specified by the last call to a {@code setBackground(…)} method. + * If {@code setBackground(…)} has not been invoked or if {@link #clear()} has been invoked after, + * then this method returns {@code null}. + * + * @return the value set by last call to {@code setBackground(…)}, or {@code null} if none or cleared. + * + * @see SampleDimension#getBackground() * - * <p>The default implementation returns {@code null}. - * Subclasses can override this method and compute a background value - * for example by analyzing the content of {@link #categories()} list.</p> + * @since 1.2 + */ + public Number getBackground() { + return toNaN.background; + } + + /** + * Sets the background value without creating a category (typically for RGB images). + * An image without category is interpreted as an image for visualization purposes only, + * without values that we can associate to a unit of measurement or a qualitative meaning. + * This {@code setBackground(Number)} method can be invoked when the caller nevertheless + * wants to declare a background value used for filling empty spaces (e.g. in image corners). * - * @return the default background value, or {@code null} if none. + * @param sample the background value, or {@code null} if none. + * @return {@code this}, for method call chaining. * * @since 1.2 */ - protected Number defaultBackground() { - return null; + public Builder setBackground(final Number sample) { + toNaN.background = sample; + return this; } /** @@ -705,7 +734,7 @@ public class SampleDimension implements Serializable { * (previous values become ordinary qualitative categories). * * @param name the category name as a {@link String} or {@link InternationalString} object, - * or {@code null} for a default "fill value" name. + * or {@code null} for a default "fill value" (localized) name. * @param sample the background value. * @return {@code this}, for method call chaining. */ @@ -998,19 +1027,12 @@ public class SampleDimension implements Serializable { ArgumentChecks.ensureNonNull("samples", samples); ArgumentChecks.ensureNonNull("converted", converted); /* - * We need to perform calculation using the same "included versus excluded" characteristics for sample and converted - * values. We pickup the characteristics of the range using floating point values because it is easier to adjust the - * bounds of the range using integer values (we just add or subtract 1 for integers, while the amount to add to real - * numbers is not so clear). If both ranges use floating point values, arbitrarily adjust the converted values. + * We need to perform calculation using the same "included versus excluded" characteristics for sample + * and converted values. We pickup the characteristics of the sample values range (except for avoiding + * a division by zero) because it is the range that describes the source data. */ - final boolean isMinIncluded, isMaxIncluded; - if (Numbers.isInteger(samples.getElementType())) { - isMinIncluded = converted.isMinIncluded(); // This is the usual case. - isMaxIncluded = converted.isMaxIncluded(); - } else { - isMinIncluded = samples.isMinIncluded(); // Less common case. - isMaxIncluded = samples.isMaxIncluded(); - } + final boolean isMinIncluded = samples.isMinIncluded(); + final boolean isMaxIncluded = samples.isMaxIncluded() && samples.getSpan() > 0; // `isEmpty()` is not sufficient. final double minValue = converted.getMinDouble(isMinIncluded); final double Δvalue = converted.getMaxDouble(isMaxIncluded) - minValue; final double minSample = samples.getMinDouble(isMinIncluded); @@ -1175,9 +1197,6 @@ defName: if (name == null) { } name = createLocalName(Vocabulary.formatInternational(Vocabulary.Keys.Untitled)); } - if (toNaN.background == null) { - toNaN.background = defaultBackground(); - } return new SampleDimension(name, toNaN.background, UnmodifiableArrayList.wrap(categories, 0, count)); } 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 4131f8a..c0121ec 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 @@ -23,8 +23,6 @@ import org.apache.sis.coverage.SampleDimension; import org.apache.sis.coverage.Category; import org.apache.sis.image.ImageProcessor; import org.apache.sis.measure.NumberRange; -import org.apache.sis.internal.util.Numerics; -import org.apache.sis.util.resources.Vocabulary; import org.apache.sis.util.Static; @@ -111,43 +109,4 @@ public final class SampleDimensions extends Static { } return sampleFilters; } - - /** - * Adds categories to the given sample dimension builder for an image having no explicit transfer function. - * This method creates a default transfer function only if needed, for example if a "no data" value exists. - * If the transfer function would be identity, then this method does not add it even if {@code sampleRange} - * was non-null. This conservative policy is because the purpose of this method is only to avoid that image - * operations such as resampling do their calculations on wrong values. If we can avoid to create artificial - * information that did not existed in the original data, it is better. - * - * @param sampleSize size of sample values in bits, or 0 if unknown or if sample are floating-point values. - * @param isUnsigned whether sample values are unsigned integers. Ignored if {@code sampleSize} is 0. - * @param sampleRange minimum and maximum sample values, or {@code null} if unknown. - * @param fillValue the "no data" value, or {@code null} if none. May intersect {@code sampleRange}. - * @param dest where to add the categories. - */ - public static void addDefaultCategories(final int sampleSize, final boolean isUnsigned, NumberRange<?> sampleRange, - final Number fillValue, final SampleDimension.Builder dest) - { - if (fillValue != null) { - dest.setBackground(null, fillValue); - if (sampleRange == null && sampleSize != 0) { - long min = 0, max = Numerics.bitmask(sampleSize) - 1; - if (!isUnsigned) { - max >>>= 1; - min = ~max; - } - sampleRange = NumberRange.createBestFit(min, true, max, true); - } - if (sampleRange != null && sampleRange.containsAny(fillValue)) { - final double fill = fillValue.doubleValue(); - if (sampleRange.getMaxDouble() - fill < fill - sampleRange.getMinDouble()) { - sampleRange = NumberRange.createBestFit(sampleRange.getMinValue(), sampleRange.isMinIncluded(), fill, false); - } else { - sampleRange = NumberRange.createBestFit(fill, false, sampleRange.getMaxValue(), sampleRange.isMaxIncluded()); - } - dest.addQuantitative(Vocabulary.formatInternational(Vocabulary.Keys.Values), sampleRange, sampleRange); - } - } - } } diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java index 26e1c45..83468e2 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/Colorizer.java @@ -418,7 +418,6 @@ reuse: if (source != null) { for (int i=0; i<count; i++) { final ColorsForRange entry = entries[i]; NumberRange<?> sourceRange = entry.sampleRange; - final double s = sourceRange.getSpan(); if (!entry.isData()) { if (lower >= MAX_VALUE) { throw new IllegalArgumentException(Resources.format(Resources.Keys.TooManyQualitatives)); @@ -444,15 +443,18 @@ reuse: if (source != null) { themes = (themes != null) ? themes.unionAny(sourceRange) : sourceRange; } } - } else if (s > 0) { - // Range of real values: defer processing to next loop. - span += s; - System.arraycopy(entries, deferred, entries, deferred + 1, i - deferred); - entries[deferred++] = entry; } else { - // Invalid range: silently discard. - System.arraycopy(entries, i+1, entries, i, --count - i); - entries[count] = null; + final double s = sourceRange.getSpan(); + if (s > 0) { + // Range of real values: defer processing to next loop. + span += s; + System.arraycopy(entries, deferred, entries, deferred + 1, i - deferred); + entries[deferred++] = entry; + } else { + // Invalid range: silently discard. + System.arraycopy(entries, i+1, entries, i, --count - i); + entries[count] = null; + } } } /* diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/RenderingData.java b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/RenderingData.java index bff4c87..994ec06 100644 --- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/RenderingData.java +++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/RenderingData.java @@ -325,7 +325,7 @@ public class RenderingData implements Cloneable { } data = null; currentPyramidLevel = level; - return coverageLoader.getOrLoad(level).forConvertedValues(true); + return coverageLoader.getOrLoad(level); } /** diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/Band.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/Band.java index dd9b4dd..52aa27f 100644 --- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/Band.java +++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/Band.java @@ -193,7 +193,7 @@ final class Band extends GridResourceWrapper implements SchemaModifier { */ @Override public SampleDimension customize(final int image, final int band, final NumberRange<?> sampleRange, - final Number fillValue, final SampleDimension.Builder dimension) + final SampleDimension.Builder dimension) { if ((image | band) == 0) { dimension.setName(identifier); 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 4f0f24c..6d19bab 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 @@ -20,7 +20,6 @@ import org.apache.sis.coverage.SampleDimension; import org.apache.sis.measure.NumberRange; import org.apache.sis.metadata.iso.DefaultMetadata; import org.apache.sis.setup.OptionKey; -import org.apache.sis.internal.coverage.SampleDimensions; import org.apache.sis.internal.storage.io.InternalOptionKey; import org.apache.sis.storage.DataStoreException; import org.opengis.metadata.Metadata; @@ -76,25 +75,22 @@ public interface SchemaModifier { /** * Invoked when a sample dimension is created for a band in an image. * {@code GeoTiffStore} invokes this method with a builder initialized to band number as - * {@linkplain SampleDimension.Builder#setName(int) dimension name} and with no category. - * Implementations can override this method for setting a better name or for declaring the - * meaning of sample values (by adding "categories"). - * - * <p>The default implementation creates categories only if {@code fillValue} is non-null. - * In such case, the fill value is also defined as the background value.</p> + * {@linkplain SampleDimension.Builder#setName(int) dimension name}, with the fill value + * declared as {@linkplain SampleDimension.Builder#setBackground(Number) background} and + * with no category. Implementations can override this method for setting a better name + * or for declaring the meaning of sample values (by adding "categories"). * * @param image index of the image for which to create sample dimension. * @param band index of the band for which to create sample dimension. * @param sampleRange minimum and maximum values declared in the TIFF tags, or {@code null} if unknown. - * @param fillValue the "no data" value, or {@code null} if none. May intersect {@code sampleRange}. + * This range may contain the background value. * @param dimension a sample dimension builder initialized with band number as the dimension name. * This builder can be modified in-place. * @return the sample dimension to use. */ default SampleDimension customize(final int image, final int band, NumberRange<?> sampleRange, - final Number fillValue, final SampleDimension.Builder dimension) + final SampleDimension.Builder dimension) { - SampleDimensions.addDefaultCategories(0, false, sampleRange, fillValue, dimension); return dimension.build(); } 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 c7484ad..3e7a3f2 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 @@ -1537,10 +1537,10 @@ final class ImageFileDirectory extends DataCube { minValues.get(Math.min(band, minValues.size()-1)), true, maxValues.get(Math.min(band, maxValues.size()-1)), true); } - builder.setName(band + 1); + builder.setName(band + 1).setBackground(getFillValue(true)); final SampleDimension sd; if (isIndexValid) { - sd = reader.store.customizer.customize(index, band, sampleRange, getFillValue(true), builder); + sd = reader.store.customizer.customize(index, band, sampleRange, builder); } else { sd = builder.build(); } @@ -1692,7 +1692,7 @@ final class ImageFileDirectory extends DataCube { throw new DataStoreContentException(Errors.format(Errors.Keys.UnexpectedValueInElement_2, "numBands", numBands)); } final boolean hasAlpha = (numBands >= 4); - final boolean packed = sm instanceof SinglePixelPackedSampleModel; + final boolean packed = (sm instanceof SinglePixelPackedSampleModel); colorModel = ColorModelFactory.createRGB(bitsPerSample, packed, hasAlpha); break; } diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/RasterReader.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/RasterReader.java index 82061cd..6f7e917 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/RasterReader.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/RasterReader.java @@ -44,7 +44,6 @@ import org.apache.sis.coverage.grid.GridCoverage; 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.internal.coverage.SampleDimensions; import org.apache.sis.internal.coverage.j2d.ColorModelFactory; import org.apache.sis.internal.referencing.j2d.AffineTransform2D; import org.apache.sis.internal.storage.io.ChannelDataInput; @@ -383,9 +382,8 @@ public final class RasterReader extends RasterFormat { // See `Band.OPPOSITE_SIGN` javadoc for more information on this limitation. throw new RasterFormatException("Data type not yet supported."); } - SampleDimensions.addDefaultCategories(band.getDataTypeSize(), band.isUnsigned(), null, - band.noDataValue, builder.setName(b + 1)); - sd[b] = builder.build(); + sd[b] = builder.setName(b + 1).setBackground(band.noDataValue).build(); + builder.clear(); } range = Arrays.asList(sd); }