diff --git a/pom.xml b/pom.xml index 03b8475bc6d..4841ff7ffd1 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,7 @@ This is necessary to make NetBeans modules updatable and independent from the Maven version (= snap.version). --> ${project.version} + 2.0.05 2.7.1 5.3.1 diff --git a/snap-core/src/main/java/org/esa/snap/core/dataop/resamp/NonNegative.java b/snap-core/src/main/java/org/esa/snap/core/dataop/resamp/NonNegative.java new file mode 100644 index 00000000000..b6c42ae19d7 --- /dev/null +++ b/snap-core/src/main/java/org/esa/snap/core/dataop/resamp/NonNegative.java @@ -0,0 +1,51 @@ +package org.esa.snap.core.dataop.resamp; + +/** + * Resampling decorator. Ensures that the decorated resampling yields non-negative values + * by clipping any negative value to zero. + * + * @author Ralf Quast + */ +public class NonNegative implements Resampling { + + private final Resampling resampling; + + /** + * Creates a new "non-negative" resampling from a given resampling type. + * + * @param resampling the resampling type. + */ + public NonNegative(Resampling resampling) { + this.resampling = resampling; + } + + @Override + public String getName() { + return String.format("NON_NEGATIVE_%s", resampling.getName()); + } + + @Override + public Index createIndex() { + return resampling.createIndex(); + } + + @Override + public void computeIndex(double x, double y, int width, int height, Index index) { + resampling.computeIndex(x, y, width, height, index); + } + + @Override + public void computeCornerBasedIndex(double x, double y, int width, int height, Index index) { + resampling.computeCornerBasedIndex(x, y, width, height, index); + } + + @Override + public double resample(Raster raster, Index index) throws Exception { + return Math.max(0.0, resampling.resample(raster, index)); + } + + @Override + public int getKernelSize() { + return resampling.getKernelSize(); + } +} diff --git a/snap-core/src/main/java/org/esa/snap/core/dataop/resamp/Resampling.java b/snap-core/src/main/java/org/esa/snap/core/dataop/resamp/Resampling.java index 11206066c93..7cb56f429b7 100644 --- a/snap-core/src/main/java/org/esa/snap/core/dataop/resamp/Resampling.java +++ b/snap-core/src/main/java/org/esa/snap/core/dataop/resamp/Resampling.java @@ -43,11 +43,17 @@ public interface Resampling { Resampling BISINC_5_POINT_INTERPOLATION = new BiSinc5PointInterpolationResampling(); Resampling BISINC_11_POINT_INTERPOLATION = new BiSinc11PointInterpolationResampling(); Resampling BISINC_21_POINT_INTERPOLATION = new BiSinc21PointInterpolationResampling(); + /** * The bicubic spline interpolation resampling method. */ Resampling BICUBIC_INTERPOLATION = new BiCubicInterpolationResampling(); + /** + * The non-negative bicubic spline interpolation resampling method. + */ + Resampling NON_NEGATIVE_BICUBIC_INTERPOLATION = new NonNegative(BICUBIC_INTERPOLATION); + /** * Gets a unique identifier for this resampling method, e.g. "BILINEAR_INTERPOLATION". * diff --git a/snap-core/src/main/java/org/esa/snap/core/dataop/resamp/ResamplingFactory.java b/snap-core/src/main/java/org/esa/snap/core/dataop/resamp/ResamplingFactory.java index 9baee624842..3745f16d1a2 100644 --- a/snap-core/src/main/java/org/esa/snap/core/dataop/resamp/ResamplingFactory.java +++ b/snap-core/src/main/java/org/esa/snap/core/dataop/resamp/ResamplingFactory.java @@ -30,6 +30,7 @@ public final class ResamplingFactory { public static final String BISINC_11_POINT_INTERPOLATION_NAME = "BISINC_11_POINT_INTERPOLATION"; public static final String BISINC_21_POINT_INTERPOLATION_NAME = "BISINC_21_POINT_INTERPOLATION"; public static final String BICUBIC_INTERPOLATION_NAME = "BICUBIC_INTERPOLATION"; + public static final String NON_NEGATIVE_BICUBIC_INTERPOLATION_NAME = "NON_NEGATIVE_BICUBIC_INTERPOLATION"; public static final String[] resamplingNames = new String[]{ NEAREST_NEIGHBOUR_NAME, @@ -70,6 +71,8 @@ public static Resampling createResampling(final String resamplingName) { return Resampling.BISINC_21_POINT_INTERPOLATION; case BICUBIC_INTERPOLATION_NAME: return Resampling.BICUBIC_INTERPOLATION; + case NON_NEGATIVE_BICUBIC_INTERPOLATION_NAME: + return Resampling.NON_NEGATIVE_BICUBIC_INTERPOLATION; default: return null; } diff --git a/snap-core/src/test/java/org/esa/snap/core/dataop/resamp/NonNegativeBicubicInterpolationResamplingTest.java b/snap-core/src/test/java/org/esa/snap/core/dataop/resamp/NonNegativeBicubicInterpolationResamplingTest.java new file mode 100644 index 00000000000..80f20f79045 --- /dev/null +++ b/snap-core/src/test/java/org/esa/snap/core/dataop/resamp/NonNegativeBicubicInterpolationResamplingTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2014 Brockmann Consult GmbH (info@brockmann-consult.de) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see http://www.gnu.org/licenses/ + */ + +package org.esa.snap.core.dataop.resamp; + +import junit.framework.TestCase; + +public class NonNegativeBicubicInterpolationResamplingTest extends TestCase { + + final Resampling resampling = Resampling.NON_NEGATIVE_BICUBIC_INTERPOLATION; + final TestRaster raster = new TestRaster(); + + public void testCreateIndex() { + final Resampling.Index index = resampling.createIndex(); + assertNotNull(index); + assertNotNull(index.i); + assertNotNull(index); + assertNotNull(index.i); + assertNotNull(index.j); + assertNotNull(index.ki); + assertNotNull(index.kj); + assertEquals(2, index.i.length); + assertEquals(2, index.j.length); + assertEquals(1, index.ki.length); + assertEquals(1, index.kj.length); + } + + public void testIndexAndSample() throws Exception { + final Resampling.Index index = resampling.createIndex(); + + testIndexAndSample( + index, + 2.2f, 2.3f, + 1.0, 2.0, + 1.0, 2.0, + 0.7f, + 0.8f, + 25.0616f); + } + + private void testIndexAndSample( + final Resampling.Index index, + float x, float y, + double i1Exp, double i2Exp, + double j1Exp, double j2Exp, + float ki1Exp, + float kj1Exp, + float sampleExp) throws Exception { + + resampling.computeIndex(x, y, raster.getWidth(), raster.getHeight(), index); + + assertEquals(i1Exp, index.i[0]); + assertEquals(i2Exp, index.i[1]); + + assertEquals(j1Exp, index.j[0]); + assertEquals(j2Exp, index.j[1]); + + assertEquals(ki1Exp, index.ki[0], 1e-5f); + assertEquals(kj1Exp, index.kj[0], 1e-5f); + + double sample = resampling.resample(raster, index); + assertEquals(sampleExp, sample, 1e-5f); + } + + public void testCornerBasedIndex() throws Exception { + testCornerIndex(2.2f, 2.3f); + } + + private void testCornerIndex(final float x, final float y) { + + final Resampling.Index index = resampling.createIndex(); + resampling.computeCornerBasedIndex(x, y, raster.getWidth(), raster.getHeight(), index); + + final Resampling.Index indexExp = resampling.createIndex(); + computeExpectedIndex(x, y, raster.getWidth(), raster.getHeight(), indexExp); + + assertEquals(indexExp.i[0], index.i[0]); + assertEquals(indexExp.i[1], index.i[1]); + assertEquals(indexExp.j[0], index.j[0]); + assertEquals(indexExp.j[1], index.j[1]); + assertEquals(indexExp.ki[0], index.ki[0]); + assertEquals(indexExp.kj[0], index.kj[0]); + } + + private void computeExpectedIndex( + final double x, final double y, final int width, final int height, final Resampling.Index index) { + index.x = x; + index.y = y; + index.width = width; + index.height = height; + + + final int i0 = (int) Math.floor(x); + final int j0 = (int) Math.floor(y); + + index.i0 = i0; + index.j0 = j0; + + index.i[0] = Resampling.Index.crop(i0, width - 1); + index.i[1] = Math.min(i0 + 1, width - 1); + index.ki[0] = x - i0; + index.j[0] = Resampling.Index.crop(j0, height - 1); + index.j[1] = Math.min(j0 + 1, height - 1); + index.kj[0] = y - j0; + } +} diff --git a/snap-gpf/pom.xml b/snap-gpf/pom.xml index 89d21ea63f4..e8c7961e3e5 100644 --- a/snap-gpf/pom.xml +++ b/snap-gpf/pom.xml @@ -26,6 +26,7 @@ snap-gpf nbm + SNAP Graph Processing Framework (GPF) The basic framework for processing using Operators and the GPT. diff --git a/snap-gpf/src/main/java/org/esa/snap/core/gpf/internal/TileImpl.java b/snap-gpf/src/main/java/org/esa/snap/core/gpf/internal/TileImpl.java index 8f7414381fc..8d35f8ba177 100644 --- a/snap-gpf/src/main/java/org/esa/snap/core/gpf/internal/TileImpl.java +++ b/snap-gpf/src/main/java/org/esa/snap/core/gpf/internal/TileImpl.java @@ -121,6 +121,17 @@ public double toGeoPhysical(double sample) { return rasterDataNode.scale(sample); } + private int toRaw(int sample) { + final double rawSample = rasterDataNode.scaleInverse(sample); + if (rawSample < -2147483648.0) { + return Integer.MIN_VALUE; + } + if (rawSample > 2147483647.0) { + return Integer.MAX_VALUE; + } + return (int) Math.round(rawSample); + } + @Override public float toRaw(float sample) { return (float) rasterDataNode.scaleInverse(sample); @@ -492,7 +503,6 @@ public int getSampleInt(int x, int y) { int sample = raster.getSample(x, y, 0); // handle unsigned data types, see also [BEAM-1147] (nf - 20100527) if (signedByte) { - //noinspection SillyAssignment sample = (byte) sample; } if (scaled) { @@ -504,17 +514,39 @@ public int getSampleInt(int x, int y) { @Override public void setSample(int x, int y, int sample) { if (scaled) { - sample = (int) Math.floor(toRaw((double) sample) + 0.5); + sample = toRaw(sample); + } + switch (rasterDataNode.getDataType()) { + case ProductData.TYPE_INT8: + sample = clipOrRound(sample, -128, 127); + break; + case ProductData.TYPE_INT16: + sample = clipOrRound(sample, -32768, 32767); + break; + case ProductData.TYPE_UINT8: + sample = clipOrRound(sample, 0, 255); + break; + case ProductData.TYPE_UINT16: + sample = clipOrRound(sample, 0, 65535); + break; } writableRaster.setSample(x, y, 0, sample); } + private int clipOrRound(int sample, int lowerBound, int upperBound) { + if (sample < lowerBound) { + sample = lowerBound; + } else if (sample > upperBound) { + sample = upperBound; + } + return sample; + } + @Override public float getSampleFloat(int x, int y) { float sample = raster.getSampleFloat(x, y, 0); // handle unsigned data types, see also [BEAM-1147] (nf - 20100527) if (signedByte) { - //noinspection SillyAssignment sample = (byte) sample; } if (scaled) { @@ -528,16 +560,38 @@ public void setSample(int x, int y, float sample) { if (scaled) { sample = toRaw(sample); } + switch (rasterDataNode.getDataType()) { + case ProductData.TYPE_INT8: + sample = clipOrRound(sample, -128.0f, 127.0f); + break; + case ProductData.TYPE_INT16: + sample = clipOrRound(sample, -32768.0f, 32767.0f); + break; + case ProductData.TYPE_UINT8: + sample = clipOrRound(sample, 0.0f, 255.0f); + break; + case ProductData.TYPE_UINT16: + sample = clipOrRound(sample, 0.0f, 65535.0f); + break; + } writableRaster.setSample(x, y, 0, sample); } + private float clipOrRound(float sample, float lowerBound, float upperBound) { + if (sample < lowerBound) { + return lowerBound; + } + if (sample > upperBound) { + return upperBound; + } + return (float) Math.rint(sample); + } @Override public double getSampleDouble(int x, int y) { double sample = raster.getSampleDouble(x, y, 0); // handle unsigned data types, see also [BEAM-1147] (nf - 20100527) if (signedByte) { - //noinspection SillyAssignment sample = (byte) sample; } if (scaled) { @@ -551,9 +605,33 @@ public void setSample(int x, int y, double sample) { if (scaled) { sample = toRaw(sample); } + switch (rasterDataNode.getDataType()) { + case ProductData.TYPE_INT8: + sample = clipOrRound(sample, -128.0, 127.0); + break; + case ProductData.TYPE_INT16: + sample = clipOrRound(sample, -32768.0, 32767.0); + break; + case ProductData.TYPE_UINT8: + sample = clipOrRound(sample, 0.0, 255.0); + break; + case ProductData.TYPE_UINT16: + sample = clipOrRound(sample, 0.0, 65535.0); + break; + } writableRaster.setSample(x, y, 0, sample); } + private double clipOrRound(double sample, double lowerBound, double upperBound) { + if (sample < lowerBound) { + return lowerBound; + } + if (sample > upperBound) { + return upperBound; + } + return Math.rint(sample); + } + @Override public boolean getSampleBit(int x, int y, int bitIndex) { long sample = raster.getSample(x, y, 0); diff --git a/snap-gpf/src/test/java/org/esa/snap/core/gpf/internal/TileImplTest.java b/snap-gpf/src/test/java/org/esa/snap/core/gpf/internal/TileImplTest.java index 5f4be3fbc8b..66ef0142fee 100644 --- a/snap-gpf/src/test/java/org/esa/snap/core/gpf/internal/TileImplTest.java +++ b/snap-gpf/src/test/java/org/esa/snap/core/gpf/internal/TileImplTest.java @@ -277,8 +277,174 @@ public void testSetSamples() { samples = tile.getSamplesDouble(); assertNotNull(samples); assertEquals(N, samples.length); - assertEquals(10.0, samples[0], 1.0e-10); - assertEquals(10.0, samples[N - 1], 1.0e-10); + assertEquals(12.5, samples[0], 0.0); + assertEquals(12.5, samples[N - 1], 0.0); + } + + @Test + public void testSetSamples_Double_INT8() { + final double factor = 0.015; + final Tile tile = createScaledTile(ProductData.TYPE_INT8, factor); + + tile.setSample(0, 0, 1.0); + assertEquals(1.0, tile.getSampleDouble(0,0), 0.015 / 2.0); + + tile.setSample(0, 0, -1.0); + assertEquals(-1.0, tile.getSampleDouble(0,0), 0.015 / 2.0); + + final double upperBound = 127.0; + final double tooBig = upperBound * (factor + 0.001); + tile.setSample(0, 0, tooBig); + assertEquals(upperBound * factor, tile.getSampleDouble(0,0), 0.0); + + final double negativeTooBig = -tooBig; + final double lowerBound = -upperBound - 1.0; + tile.setSample(0, 0, negativeTooBig); + assertEquals(lowerBound * factor, tile.getSampleDouble(0,0), 0.0); + } + + @Test + public void testSetSamples_Double_INT16() { + final double factor = 0.015; + final Tile tile = createScaledTile(ProductData.TYPE_INT16, factor); + + tile.setSample(0, 0, 1.0); + assertEquals(1.0, tile.getSampleDouble(0,0), 0.015 / 2.0); + + tile.setSample(0, 0, -1.0); + assertEquals(-1.0, tile.getSampleDouble(0,0), 0.015 / 2.0); + + final double upperBound = 32767.0; + final double tooBig = upperBound * (factor + 0.001); + tile.setSample(0, 0, tooBig); + assertEquals(upperBound * factor, tile.getSampleDouble(0,0), 0.0); + + final double lowerBound = -upperBound - 1.0; + tile.setSample(0, 0, -tooBig); + assertEquals(lowerBound * factor, tile.getSampleDouble(0,0), 0.0); + } + + @Test + public void testSetSamples_Double_UINT8() { + final double factor = 0.015; + final Tile tile = createScaledTile(ProductData.TYPE_UINT8, factor); + + tile.setSample(0, 0, 1.0); + assertEquals(1.0, tile.getSampleDouble(0,0), 0.015 / 2.0); + + tile.setSample(0, 0, 0.0); + assertEquals(0.0, tile.getSampleDouble(0,0), 0.0); + + final double upperBound = 255.0; + final double tooBig = upperBound * (factor + 0.001); + tile.setSample(0, 0, tooBig); + assertEquals(upperBound * factor, tile.getSampleDouble(0,0), 0.0); + + tile.setSample(0, 0, -1.0); + assertEquals(0.0, tile.getSampleDouble(0,0), 0.0); + } + + @Test + public void testSetSamples_Double_UINT16() { + final double factor = 0.015; + final Tile tile = createScaledTile(ProductData.TYPE_UINT16, factor); + + tile.setSample(0, 0, 1.0); + assertEquals(1.0, tile.getSampleDouble(0,0), 0.015 / 2.0); + + tile.setSample(0, 0, 0.0); + assertEquals(0.0, tile.getSampleDouble(0,0), 0.0); + + final double upperBound = 65535.0; + final double tooBig = upperBound * (factor + 0.001); + tile.setSample(0, 0, tooBig); + assertEquals(upperBound * factor, tile.getSampleDouble(0,0), 0.0); + + tile.setSample(0, 0, -1.0); + assertEquals(0.0, tile.getSampleDouble(0,0), 0.0); + } + + @Test + public void testSetSamples_Float_INT8() { + final double factor = 0.015; + final Tile tile = createScaledTile(ProductData.TYPE_INT8, factor); + + tile.setSample(0, 0, 1.0f); + assertEquals(1.0, tile.getSampleFloat(0,0), 0.015 / 2.0); + + tile.setSample(0, 0, -1.0f); + assertEquals(-1.0, tile.getSampleFloat(0,0), 0.015 / 2.0); + + final double upperBound = 127.0; + final double tooBig = upperBound * (factor + 0.001); + tile.setSample(0, 0, (float) tooBig); + assertEquals((float) (upperBound * factor), tile.getSampleFloat(0,0), 0.0); + + final double negativeTooBig = -tooBig; + final double lowerBound = -upperBound - 1.0; + tile.setSample(0, 0, (float) negativeTooBig); + assertEquals((float) (lowerBound * factor), tile.getSampleFloat(0,0), 0.0); + } + + @Test + public void testSetSamples_Float_INT16() { + final double factor = 0.015; + final Tile tile = createScaledTile(ProductData.TYPE_INT16, factor); + + tile.setSample(0, 0, 1.0f); + assertEquals(1.0, tile.getSampleFloat(0,0), 0.015 / 2.0); + + tile.setSample(0, 0, -1.0f); + assertEquals(-1.0, tile.getSampleFloat(0,0), 0.015 / 2.0); + + final double upperBound = 32767.0; + final double tooBig = upperBound * (factor + 0.001); + tile.setSample(0, 0, (float) tooBig); + assertEquals((float) (upperBound * factor), tile.getSampleFloat(0,0), 0.0); + + final double lowerBound = -upperBound - 1.0; + tile.setSample(0, 0, (float) -tooBig); + assertEquals((float) (lowerBound * factor), tile.getSampleFloat(0,0), 0.0); + } + + @Test + public void testSetSamples_Float_UINT8() { + final double factor = 0.015; + final Tile tile = createScaledTile(ProductData.TYPE_UINT8, factor); + + tile.setSample(0, 0, 1.0f); + assertEquals(1.0, tile.getSampleFloat(0,0), 0.015 / 2.0); + + tile.setSample(0, 0, 0.0f); + assertEquals(0.0, tile.getSampleFloat(0,0), 0.0); + + final double upperBound = 255.0; + final double tooBig = upperBound * (factor + 0.001); + tile.setSample(0, 0, (float) tooBig); + assertEquals((float) (upperBound * factor), tile.getSampleFloat(0,0), 0.0); + + tile.setSample(0, 0, -1.0f); + assertEquals(0.0, tile.getSampleFloat(0,0), 0.0); + } + + @Test + public void testSetSamples_Float_UINT16() { + final double factor = 0.015; + final Tile tile = createScaledTile(ProductData.TYPE_UINT16, factor); + + tile.setSample(0, 0, 1.0f); + assertEquals(1.0, tile.getSampleFloat(0,0), 0.015 / 2.0); + + tile.setSample(0, 0, 0.0f); + assertEquals(0.0, tile.getSampleFloat(0,0), 0.0); + + final double upperBound = 65535.0; + final double tooBig = upperBound * (factor + 0.001); + tile.setSample(0, 0, (float) tooBig); + assertEquals((float) (upperBound * factor), tile.getSampleFloat(0,0), 0.0); + + tile.setSample(0, 0, -1.0f); + assertEquals(0.0, tile.getSampleFloat(0,0), 0.0); } static Tile createRawTile(int type) {