sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: First draft of an image providing error estimations for each pixel of a resampled image.
Date Wed, 10 Jun 2020 17:34:34 GMT
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 c8d34c8  First draft of an image providing error estimations for each pixel of a
resampled image.
c8d34c8 is described below

commit c8d34c8110e8b50ba33db85a470ef756f209824c
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Jun 10 19:34:04 2020 +0200

    First draft of an image providing error estimations for each pixel of a resampled image.
---
 .../java/org/apache/sis/image/PlanarImage.java     |   3 +
 .../org/apache/sis/image/PositionalErrorImage.java | 166 +++++++++++++++++++++
 .../java/org/apache/sis/image/ResampledImage.java  |  69 +++++++--
 3 files changed, 224 insertions(+), 14 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
index 618dc9a..4344a71 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PlanarImage.java
@@ -194,6 +194,9 @@ public abstract class PlanarImage implements RenderedImage {
      *     <td>{@value #STATISTICS_KEY}</td>
      *     <td>Minimum, maximum and mean values for each band.</td>
      *   </tr><tr>
+     *     <td>{@value ResampledImage#POSITIONAL_ERRORS_KEY}</td>
+     *     <td>Estimation of positional error for each pixel.</td>
+     *   </tr><tr>
      *     <td>{@value ComputedImage#SOURCE_PADDING_KEY}</td>
      *     <td>Amount of additional source pixels needed on each side of a destination
pixel for computing its value.</td>
      *   </tr>
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PositionalErrorImage.java
b/core/sis-feature/src/main/java/org/apache/sis/image/PositionalErrorImage.java
new file mode 100644
index 0000000..8e4d7fe
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PositionalErrorImage.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.image;
+
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.PixelInterleavedSampleModel;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+import java.awt.image.WritableRaster;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
+import org.apache.sis.util.Workaround;
+
+
+/**
+ * Estimation of positional error for each pixel in an image computed by {@link ResampledImage}.
+ * This is the implementation of {@link ResampledImage#POSITIONAL_ERRORS_KEY} property value.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class PositionalErrorImage extends ComputedImage {
+    /**
+     * The color model for this image.
+     * Arbitrarily configured for an error range from 0 to 1.
+     */
+    private final ColorModel colorModel;
+
+    /**
+     * Domain of pixel coordinates in this image.
+     */
+    private final int minX, minY, width, height;
+
+    /**
+     * A copy of {@link ResampledImage#toSourceSupport} with the support translation removed.
+     * The result may be different than {@link ResampledImage#toSource} if the transform
has
+     * been replaced by {@link ResamplingGrid}.
+     */
+    private final MathTransform toSource;
+
+    /**
+     * The inverse of {@link ResampledImage#toSource}. Should not be concatenated with {@link
#toSource}
+     * because optimizations applied by Apache SIS during concatenations may hide the errors
that we want
+     * to visualize.
+     */
+    private final MathTransform toTarget;
+
+    /**
+     * Creates a new instance for the given image.
+     */
+    PositionalErrorImage(final ResampledImage image, final MathTransform toSource) throws
TransformException {
+        super(createSampleModel(image.getSampleModel()), image);
+        this.toSource = toSource;
+        this.toTarget = image.toSource.inverse();
+        this.minX     = image.getMinX();
+        this.minY     = image.getMinY();
+        this.width    = image.getWidth();
+        this.height   = image.getHeight();
+        colorModel    = ColorModelFactory.createGrayScale(DataBuffer.TYPE_FLOAT, 1, 0, 0,
1);
+    }
+
+    /**
+     * Creates the sample model. This is a workaround for RFE #4093999
+     * ("Relax constraint on placement of this()/super() call in constructors").
+     */
+    @Workaround(library="JDK", version="1.8")
+    private static SampleModel createSampleModel(final SampleModel origin) {
+        final int width = origin.getWidth();
+        return new PixelInterleavedSampleModel(DataBuffer.TYPE_FLOAT, width, origin.getHeight(),
1, width, new int[1]);
+    }
+
+    /**
+     * Returns an arbitrary color model for this image.
+     */
+    @Override
+    public ColorModel getColorModel() {
+        return colorModel;
+    }
+
+    /**
+     * Returns the minimum <var>x</var> coordinate (inclusive) of this image.
+     */
+    @Override
+    public final int getMinX() {
+        return minX;
+    }
+
+    /**
+     * Returns the minimum <var>y</var> coordinate (inclusive) of this image.
+     */
+    @Override
+    public final int getMinY() {
+        return minY;
+    }
+
+    /**
+     * Returns the number of columns in this image.
+     */
+    @Override
+    public final int getWidth() {
+        return width;
+    }
+
+    /**
+     * Returns the number of rows in this image.
+     */
+    @Override
+    public final int getHeight() {
+        return height;
+    }
+
+    /**
+     * Invoked when a tile need to be computed or updated.
+     *
+     * @param  tileX  the column index of the tile to compute.
+     * @param  tileY  the row index of the tile to compute.
+     * @param  tile   if the tile already exists but needs to be updated, the tile to update.
Otherwise {@code null}.
+     * @return computed tile for the given indices.
+     * @throws TransformException if an error occurred while computing pixel coordinates.
+     */
+    @Override
+    protected Raster computeTile(final int tileX, final int tileY, WritableRaster tile) throws
TransformException {
+        if (tile == null) {
+            tile = createTile(tileX, tileY);
+        }
+        final int scanline = tile.getWidth();
+        final int tileMinX = tile.getMinX();
+        final int tileMinY = tile.getMinY();
+        final int tileMaxX = Math.addExact(tileMinX, scanline);
+        final int tileMaxY = Math.addExact(tileMinY, tile.getHeight());
+        final double[] buffer = new double[scanline * Math.max(ResampledImage.BIDIMENSIONAL,
toSource.getSourceDimensions())];
+        for (int y=tileMinY; y<tileMaxY; y++) {
+            for (int i=0, x = tileMinX; x < tileMaxX; x++) {
+                buffer[i++] = x;
+                buffer[i++] = y;
+            }
+            toSource.transform(buffer, 0, buffer, 0, scanline);
+            toTarget.transform(buffer, 0, buffer, 0, scanline);
+            for (int i=0, x = tileMinX; x < tileMaxX; x++) {
+                final int t = i;
+                buffer[t] = Math.hypot(buffer[i++] - x,
+                                       buffer[i++] - y);
+            }
+            tile.setSamples(tileMinX, y, scanline, 1, 0, buffer);
+        }
+        return tile;
+    }
+}
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 60e9413..4b5ed54 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
@@ -39,6 +39,7 @@ import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.geometry.Shapes2D;
@@ -82,9 +83,21 @@ public class ResampledImage extends ComputedImage {
     private static final Set<String> FILTERED_PROPERTIES = Collections.singleton(SAMPLE_RESOLUTIONS_KEY);
 
     /**
+     * Key of a property providing an estimation of positional error for each pixel.
+     * Values should be instances of {@link RenderedImage} with same size and origin than
this image.
+     * The image should contain a single band where all sample values are error estimations
in pixel units
+     * (relative to pixels of this image). The value should be small, for example between
0 and 0.2.
+     *
+     * <p>The default implementation transforms all pixel coordinates {@linkplain #toSource
to source},
+     * then convert them back to pixel coordinates in this image. The result is compared
with expected
+     * coordinates and the distance is stored in the image.</p>
+     */
+    public static final String POSITIONAL_ERRORS_KEY = "org.apache.sis.PositionalErrors";
+
+    /**
      * The {@value} value for identifying code expecting exactly 2 dimensions.
      */
-    private static final int BIDIMENSIONAL = 2;
+    static final int BIDIMENSIONAL = 2;
 
     /**
      * Domain of pixel coordinates in this image.
@@ -127,6 +140,14 @@ public class ResampledImage extends ComputedImage {
     private final Object fillValues;
 
     /**
+     * {@link #POSITIONAL_ERRORS_KEY} value, computed when first requested.
+     *
+     * @see #getPositionalErrors()
+     * @see #getProperty(String)
+     */
+    private RenderedImage positionalErrors;
+
+    /**
      * Creates a new image which will resample the given image. The resampling operation
is defined
      * by a non-linear transform from <em>this</em> image to the specified <em>source</em>
image.
      * That transform should map {@linkplain org.opengis.referencing.datum.PixelInCell#CELL_CENTER
pixel centers}.
@@ -251,6 +272,21 @@ public class ResampledImage extends ComputedImage {
     }
 
     /**
+     * Computes the {@link #POSITIONAL_ERRORS_KEY} value.
+     */
+    private synchronized RenderedImage getPositionalErrors() throws TransformException {
+        if (positionalErrors == null) {
+            final Dimension s = interpolation.getSupportSize();
+            final double[] offset = new double[toSource.getSourceDimensions()];
+            offset[0] = -interpolationSupportOffset(s.width);
+            offset[1] = -interpolationSupportOffset(s.height);
+            final MathTransform tr = MathTransforms.concatenate(toSource, MathTransforms.translation(offset));
+            positionalErrors = new PositionalErrorImage(this, tr);
+        }
+        return positionalErrors;
+    }
+
+    /**
      * Verifies whether image layout information are consistent. This method performs all
verifications
      * {@linkplain ComputedImage#verify() documented in parent class}, then verifies that
source coordinates
      * required by this image (computed by converting {@linkplain #getBounds() this image
bounds} using the
@@ -319,9 +355,12 @@ public class ResampledImage extends ComputedImage {
     public Object getProperty(final String key) {
         if (FILTERED_PROPERTIES.contains(key)) {
             return getSource().getProperty(key);
-        } else {
-            return super.getProperty(key);
+        } else if (POSITIONAL_ERRORS_KEY.equals(key)) try {
+            return getPositionalErrors();
+        } catch (TransformException | IllegalArgumentException e) {
+            throw (ImagingOpException) new ImagingOpException(e.getMessage()).initCause(e);
         }
+        return super.getProperty(key);
     }
 
     /**
@@ -333,19 +372,21 @@ public class ResampledImage extends ComputedImage {
      */
     @Override
     public String[] getPropertyNames() {
-        final String[] names = getSource().getPropertyNames();      // Array should be a
copy, so we don't copy again.
-        if (names != null) {
-            int n = 0;
-            for (final String name : names) {
-                if (FILTERED_PROPERTIES.contains(name)) {
-                    names[n++] = name;
-                }
-            }
-            if (n != 0) {
-                return ArraysExt.resize(names, n);
+        int n = 0;
+        String[] names = getSource().getPropertyNames();    // Array should be a copy, so
we don't copy again.
+        if (names == null) {
+            names = CharSequences.EMPTY_ARRAY;
+        } else for (final String name : names) {
+            if (FILTERED_PROPERTIES.contains(name)) {
+                names[n++] = name;
             }
         }
-        return null;
+        if (n < names.length) {
+            names[n++] = POSITIONAL_ERRORS_KEY;
+            return ArraysExt.resize(names, n);
+        } else {
+            return ArraysExt.append(names, POSITIONAL_ERRORS_KEY);
+        }
     }
 
     /**


Mime
View raw message