Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
This is necessary to make NetBeans modules updatable and independent from the Maven version (= snap.version).
-->
<snap.version>${project.version}</snap.version>
<!-- <snap.version>8.0.3</snap.version> --> <!-- to deploy as a module update -->
<javahelp.version>2.0.05</javahelp.version>
<hdf.version>2.7.1</hdf.version>
<netcdf.version>5.3.1</netcdf.version>
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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".
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
1 change: 1 addition & 0 deletions snap-gpf/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

<artifactId>snap-gpf</artifactId>
<packaging>nbm</packaging>
<!-- <version>8.0.3.1</version> --> <!-- to deploy as a module update -->

<name>SNAP Graph Processing Framework (GPF)</name>
<description>The basic framework for processing using Operators and the GPT.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which miracle Number ist this?
Why not
if (rawSample < Integer.MIN_VALUE)

return Integer.MIN_VALUE;
}
if (rawSample > 2147483647.0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the same as above

return Integer.MAX_VALUE;
}
return (int) Math.round(rawSample);
}

@Override
public float toRaw(float sample) {
return (float) rasterDataNode.scaleInverse(sample);
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only a clip without round

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) {
Expand All @@ -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) {
Expand All @@ -551,9 +605,33 @@ public void setSample(int x, int y, double sample) {
if (scaled) {
sample = toRaw(sample);
}
switch (rasterDataNode.getDataType()) {
Copy link
Contributor

@SabineEmbacher SabineEmbacher Feb 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If raw data run into a clipping there must be something wrong with geopysical data because they run outside the covered valid value range, defined by raw data type and scaling. In this case clipping the raw data value then turns a wrong geophysical value into a valid value.
This can not be right.

Clipping or other types of handling values outside the range should be done at the place where the TileImpl is used, because only the context knows how to handle values outside the range.

Clipping then should be done that way:
First calculate the geopysical clipping bound by scaling the raw min and raw max value (take care if signed or unsigned), then clip the geopysical.
Up to here al computations outside of TileImpl.
Then transform to raw and round to prevent the cast error.

An integer cast is neither a floor nor a round nor a cail operation.
A cast on a positive double value is equal to a floor operation.
But on negative values it is equal to a cail operation.
It changes the direction of the value change for negative numbers.

Copy link
Contributor Author

@heptaflar heptaflar Feb 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree.

  1. A geophysical value that is outside the range of the raw data type is not invalid. It is just too large to be stored. It is better to clip it than to pass it uncontrolled.
  2. Casting of a double or float to a short or byte yields rather weird results. Cast of a positive double or float to a short or byte is not equal to a floor operation. Test it yourself.
  3. A scientist developing an operator probably does not want to bother with clipping and data types. Often the data type is predetermined (from external data) and not always the same.
  4. The API documentation of setSample states that the values are clipped, which they are not.
  5. A scientist's expectation probably is: he ensures that numbers (double or float) are calculated correctly. How these values are encoded into an image or a file and decoded again is business of the software framework. Encoding may not be lossless. But if something cannot be encoded without loss, the software framework should handle it in a smart and easily predictible way that is as good an approximation to the original information as possible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. A geophysical value that is outside the range of the raw data type is not invalid. It is just too large to be stored. It is better to clip it than to pass it uncontrolled.

Thats exactly what I meant.
Values are invalid from the point of view of "which data can be saved" not from the Point of view, does the value make sence from the scientific poit of view of valid data range.

If the value is in a scientific valid range, but can not be saved, then the configuration of the store must be wrong. Then either the raw data type or the scaling must be wrong.

If a value is a scientific valid value, a raw clipping changes this scientific valid value. Does this make sence?

Ideally a raw date store should not manipulate data. Also a raw data store shall not know anything of the valid scientific value range. A raw data store shall only be able to tore store all the scientivic valid data of a cerain use case. For this a raw data store has to be configured the right way. Then scientific valid data can be stored using the scaling but without manipulation.

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);
Expand Down
Loading