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 485df91 Add nearest-neighbor interpolation.
485df91 is described below
commit 485df91de868ce06bd36eff9df23b8c0af7155f2
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Fri Mar 27 15:53:43 2020 +0100
Add nearest-neighbor interpolation.
---
.../java/org/apache/sis/image/Interpolation.java | 29 ++++-
.../java/org/apache/sis/image/ResampledImage.java | 134 +++++++++++++--------
.../org/apache/sis/image/ResampledImageTest.java | 24 ++++
3 files changed, 132 insertions(+), 55 deletions(-)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java b/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java
index 2f14ee0..6cf39e8 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/Interpolation.java
@@ -101,6 +101,31 @@ public interface Interpolation {
boolean interpolate(DoubleBuffer source, int numBands, double xfrac, double yfrac, double[]
writeTo, int writeToOffset);
/**
+ * A nearest-neighbor interpolation using 1×1 pixel.
+ */
+ Interpolation NEAREST = new Interpolation() {
+ /** Interpolation name for debugging purpose. */
+ @Override public String toString() {
+ return "NEAREST";
+ }
+
+ /** Size of the area over which to provide values. */
+ @Override public Dimension getSupportSize() {
+ return new Dimension(1,1);
+ }
+
+ /** Applies nearest-neighbor interpolation on 1×1 window. */
+ @Override public boolean interpolate(final DoubleBuffer source, final int numBands,
+ final double xfrac, final double yfrac, final double[] writeTo, int writeToOffset)
+ {
+ source.mark();
+ source.get(writeTo, writeToOffset, numBands);
+ source.reset();
+ return true;
+ }
+ };
+
+ /**
* A bilinear interpolation using 2×2 pixels.
* If the interpolation result is NaN, this method fallbacks on nearest-neighbor.
*/
@@ -110,12 +135,12 @@ public interface Interpolation {
return "BILINEAR";
}
- /** Size of the area over which to provide values.*/
+ /** Size of the area over which to provide values. */
@Override public Dimension getSupportSize() {
return new Dimension(2,2);
}
- /** Applies bilinear interpolation. */
+ /** Applies bilinear interpolation on a 2×2 window. */
@Override public boolean interpolate(final DoubleBuffer source, final int numBands,
final double xfrac, final double yfrac, final double[] writeTo, int writeToOffset)
{
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
index ca61728..745de69 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ResampledImage.java
@@ -341,6 +341,8 @@ public class ResampledImage extends ComputedImage {
* for each pixel. We use integer values if possible because `WritableRaster.setPixels(…)`
implementations
* have optimizations for this case. If data are not integers, then we fallback on
non-optimized `double[]`.
*/
+ double[] transfer = null;
+ int[] intTransfer = null;
final double[] values;
final int[] intValues;
final Object valuesArray;
@@ -385,67 +387,93 @@ public class ResampledImage extends ComputedImage {
}
toSource.transform(coordinates, 0, coordinates, 0, scanline);
/*
- * Interpolate values for all bands in current scanline. The (x,y) values are
coordinates
- * in the source image and (xf,yf) are their fractional parts. Those fractional
parts are
- * between 0 inclusive and 1 exclusive except on the image borders: on the left
and upper
- * sides the fractional parts can go down to -0.5, because 0 is for pixel center
and -0.5
- * is at image border. On the right and bottom sides the fractional parts are
constrained
- * to +0.5 in nearest-neighbor interpolation case, for the same reason than other
borders.
- * However if the interpolation is bilinear, then the fractional parts on the
bottom and
- * right borders can go up to 1.5 because `PixelIterator` has reduced the (xmax,
ymax)
- * values by 1 (for taking in account the padding needed for interpolation support).
- * This tolerance can be generalized (2.5, 3.5, etc.) depending on interpolation
method.
+ * Special case for nearest-neighbor.
*/
- int ci = 0; // Index in `coordinates` array.
- int vi = 0; // Index in `values` or `intValues` array.
- for (int tx=tileMinX; tx<tileMaxX; tx++, ci+=tgtDim, vi+=numBands) {
- double x = coordinates[ci];
- if (x <= xlim) {
- // Separate integer and fractional parts with 0 ≤ xf < 1 except
on borders.
- final double xf = x - (x = Math.max(xmin, Math.min(xmax, Math.floor(x))));
- if (xf >= xoff) { // Negative only on left image
border.
- double y = coordinates[ci+1];
- if (y <= ylim) {
- // Separate integer and fractional parts with 0 ≤ yf < 1
except on borders.
- final double yf = y - (y = Math.max(ymin, Math.min(ymax, Math.floor(y))));
- if (yf >= yoff) { // Negative only on upper
image border.
- /*
- * At this point we determined that (x,y) coordinates are
inside source image domain.
- * Those coordinates may have been slightly shifted for interpolation
support if they
- * were close to an image border. If the MathTransform produced
3 or more coordinates,
- * current implementation does not yet use those coordinates.
But if we want to use
- * them in a future version (e.g. for interpolation in 3D
cube), it would be there.
- */
- if (sx != (sx = (int) x) | // Really |, not ||.
- sy != (sy = (int) y))
- {
- it.moveTo(sx, sy);
- buffer.update();
- }
- /*
- * Interpolate the values at current position. We don't do
any special processing
- * for NaN values because we want to keep them if output
type is floating point,
- * and NaN values should not occur if data type (input and
output) is integer.
- */
- if (interpolation.interpolate(buffer.values, numBands, xf,
yf, values, isInteger ? 0 : vi)) {
- if (isInteger) {
- for (int b=0; b<numBands; b++) {
- intValues[vi+b] = (int) Math.max(minValues[b],
- Math.min(maxValues[b],
Math.round(values[b])));
+ if (interpolation == Interpolation.NEAREST) {
+ int ci = 0; // Index in `coordinates` array.
+ int vi = 0; // Index in `values` or `intValues` array.
+ for (int tx=tileMinX; tx<tileMaxX; tx++, ci+=tgtDim, vi+=numBands) {
+ final long x = Math.round(coordinates[ci]);
+ if (x >= it.lowerX && x < it.upperX) {
+ final long y = Math.round(coordinates[ci+1]);
+ if (y >= it.lowerY && y < it.upperY) {
+ it.moveTo((int) x, (int) y);
+ if (isInteger) {
+ intTransfer = it.getPixel(intTransfer);
+ System.arraycopy(intTransfer, 0, intValues, vi, numBands);
+ } else {
+ transfer = it.getPixel(transfer);
+ System.arraycopy(transfer, 0, values, vi, numBands);
+ }
+ continue; // Values have been set, move to next pixel.
+ }
+ }
+ System.arraycopy(fillValues, 0, valuesArray, vi, numBands);
+ }
+ } else {
+ /*
+ * Interpolate values for all bands in current scanline. The (x,y) values
are coordinates
+ * in the source image and (xf,yf) are their fractional parts. Those fractional
parts are
+ * between 0 inclusive and 1 exclusive except on the image borders: on the
left and upper
+ * sides the fractional parts can go down to -0.5, because 0 is for pixel
center and -0.5
+ * is at image border. On the right and bottom sides the fractional parts
are constrained
+ * to +0.5 in nearest-neighbor interpolation case, for the same reason than
other borders.
+ * However if the interpolation is bilinear, then the fractional parts on
the bottom and
+ * right borders can go up to 1.5 because `PixelIterator` has reduced the
(xmax, ymax)
+ * values by 1 (for taking in account the padding needed for interpolation
support).
+ * This tolerance can be generalized (2.5, 3.5, etc.) depending on interpolation
method.
+ */
+ int ci = 0; // Index in `coordinates` array.
+ int vi = 0; // Index in `values` or `intValues` array.
+ for (int tx=tileMinX; tx<tileMaxX; tx++, ci+=tgtDim, vi+=numBands) {
+ double x = coordinates[ci];
+ if (x <= xlim) {
+ // Separate integer and fractional parts with 0 ≤ xf < 1 except
on borders.
+ final double xf = x - (x = Math.max(xmin, Math.min(xmax, Math.floor(x))));
+ if (xf >= xoff) { // Negative only on left image
border.
+ double y = coordinates[ci+1];
+ if (y <= ylim) {
+ // Separate integer and fractional parts with 0 ≤ yf <
1 except on borders.
+ final double yf = y - (y = Math.max(ymin, Math.min(ymax,
Math.floor(y))));
+ if (yf >= yoff) { // Negative only on
upper image border.
+ /*
+ * At this point we determined that (x,y) coordinates
are inside source image domain.
+ * Those coordinates may have been slightly shifted for
interpolation support if they
+ * were close to an image border. If the MathTransform
produced 3 or more coordinates,
+ * current implementation does not yet use those coordinates.
But if we want to use
+ * them in a future version (e.g. for interpolation in
3D cube), it would be there.
+ */
+ if (sx != (sx = (int) x) | // Really |, not ||.
+ sy != (sy = (int) y))
+ {
+ it.moveTo(sx, sy);
+ buffer.update();
+ }
+ /*
+ * Interpolate the values at current position. We don't
do any special processing
+ * for NaN values because we want to keep them if output
type is floating point,
+ * and NaN values should not occur if data type (input
and output) is integer.
+ */
+ if (interpolation.interpolate(buffer.values, numBands,
xf, yf, values, isInteger ? 0 : vi)) {
+ if (isInteger) {
+ for (int b=0; b<numBands; b++) {
+ intValues[vi+b] = (int) Math.max(minValues[b],
+ Math.min(maxValues[b],
Math.round(values[b])));
+ }
}
+ continue; // Values have been set, move to
next pixel.
}
- continue; // Values have been set, move to next
pixel.
}
}
}
}
+ /*
+ * If we reach this point then any of the "if" conditions above failed
+ * (i.e. the point to interpolate are outside the source image bounds)
+ * and no values have been set in the `values` or `intValues` array.
+ */
+ System.arraycopy(fillValues, 0, valuesArray, vi, numBands);
}
- /*
- * If we reach this point then any of the "if" conditions above failed
- * (i.e. the point to interpolate are outside the source image bounds)
- * and no values have been set in the `values` or `intValues` array.
- */
- System.arraycopy(fillValues, 0, valuesArray, vi, numBands);
}
/*
* At this point we finished to compute the value of a scanline.
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java
index 44ec686..b3d9d10 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/ResampledImageTest.java
@@ -179,6 +179,30 @@ public final strictfp class ResampledImageTest extends TestCase {
}
/**
+ * Tests {@link Interpolation#NEAREST} of floating point values.
+ *
+ * @throws NoninvertibleTransformException if the test did not setup the transform correctly.
+ */
+ @Test
+ public void testNearestOnFloats() throws NoninvertibleTransformException {
+ source = createImage(DataBuffer.TYPE_FLOAT);
+ createScaledByTwo(Interpolation.BILINEAR, -30, 12);
+ verifyAtIntegerPositions();
+ }
+
+ /**
+ * Tests {@link Interpolation#NEAREST} of integer values.
+ *
+ * @throws NoninvertibleTransformException if the test did not setup the transform correctly.
+ */
+ @Test
+ public void testNearestOnIntegers() throws NoninvertibleTransformException {
+ source = createImage(DataBuffer.TYPE_SHORT);
+ createScaledByTwo(Interpolation.BILINEAR, 18, 20);
+ verifyAtIntegerPositions();
+ }
+
+ /**
* Tests {@link Interpolation#BILINEAR} of floating point values.
*
* @throws NoninvertibleTransformException if the test did not setup the transform correctly.
|