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: Begin the port from Geotk project of the code using a precomputed grid of coordinate values for image resampling operations.
Date Thu, 02 Apr 2020 20:47:05 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 e52cc8e  Begin the port from Geotk project of the code using a precomputed grid of
coordinate values for image resampling operations.
e52cc8e is described below

commit e52cc8ed8beba3c2ded48e216192fbba74920835
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Thu Apr 2 22:46:04 2020 +0200

    Begin the port from Geotk project of the code using a precomputed grid of coordinate values
for image resampling operations.
---
 .../java/org/apache/sis/image/ResamplingGrid.java  | 428 +++++++++++++++++++++
 .../org/apache/sis/image/ResamplingGridTest.java   |  55 +++
 .../apache/sis/test/suite/FeatureTestSuite.java    |   1 +
 3 files changed, 484 insertions(+)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ResamplingGrid.java b/core/sis-feature/src/main/java/org/apache/sis/image/ResamplingGrid.java
new file mode 100644
index 0000000..e8c85f0
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ResamplingGrid.java
@@ -0,0 +1,428 @@
+/*
+ * 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.Dimension;
+import java.awt.Rectangle;
+import java.awt.geom.Point2D;
+import java.awt.geom.AffineTransform;
+import java.awt.image.ImagingOpException;
+import org.opengis.referencing.operation.MathTransform2D;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.referencing.operation.matrix.Matrix2;
+import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+
+import static java.lang.Math.abs;
+import static java.lang.Math.rint;
+
+
+/**
+ * A grid of precomputed pixel coordinates in source images. This grid is used during
+ * image resampling operations for avoiding to project the coordinates of every pixels
+ * when a bilinear interpolation between nearby pixels would be sufficient.
+ *
+ * <p>The grid contains transformed coordinated from <cite>target</cite>
to <cite>source</cite> grid.
+ * Those coordinates map pixel centers.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @author  Remi Marechal (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class ResamplingGrid {
+    /**
+     * The minimal width and height in pixels. If a block width or height is lower than
+     * this threshold, we will abandon the attempt to create a {@link ResamplingGrid}.
+     */
+    private static final int MIN_SIZE = 4;
+
+    /**
+     * The maximal error allowed, in units of destination CRS (usually pixels).
+     * This is the maximal difference allowed between a coordinate transformed
+     * using the original transform and the same coordinate transformed using this grid.
+     */
+    private static final double TOLERANCE = 0.25;
+
+    /**
+     * A small tolerance factor for comparisons of floating point numbers. We use the smallest
+     * accuracy possible for the {@code float} type for integer numbers different than zero,
+     * as computed by:
+     *
+     * {@preformat java
+     *     Math.nextUp(1f) - 1f;
+     * }
+     */
+    static final double EPS = 1.1920929E-7;
+
+    /**
+     * The (x,y) coordinate of the upper-left corner in the source grid.
+     */
+    private final int xmin, ymin;
+
+    /**
+     * Cells size, in number of pixels in the same units than (x,y).
+     */
+    private final int xStep, yStep;
+
+    /**
+     * Number of cells.
+     */
+    private final int xNumCells, yNumCells;
+
+    /**
+     * Sequence of (x,y) grid coordinates, in row-major fashion.
+     */
+    private final double[] warpPositions;
+
+    /**
+     * Creates a new instance for the given coordinates.
+     */
+    private ResamplingGrid(final int xmin, final int xStep, final int xNumCells,
+                           final int ymin, final int yStep, final int yNumCells,
+                           final double[] warpPositions)
+    {
+        this.xmin      = xmin;
+        this.ymin      = ymin;
+        this.xStep     = xStep;
+        this.yStep     = yStep;
+        this.xNumCells = xNumCells;
+        this.yNumCells = yNumCells;
+        this.warpPositions = warpPositions;
+    }
+
+    /**
+     * Creates a grid for the given domain of validity.
+     *
+     * @param  transform  transform from target grid corner to source grid corner.
+     * @param  domain     the domain of validity in source coordinates.
+     * @return a precomputed grid for the given transform.
+     * @throws TransformException if a derivative can not be computed or a point can not
be transformed.
+     * @throws ImagingOpException if the grid would be too big for being useful.
+     */
+    static MathTransform2D create(final MathTransform2D transform, final Rectangle domain)
throws TransformException {
+        final double xmin = domain.getMinX();
+        final double xmax = domain.getMaxX();
+        final double ymin = domain.getMinY();
+        final double ymax = domain.getMaxY();
+        final Point2D.Double point = new Point2D.Double();              // Multi-purpose
buffer.
+        final Matrix2 upperLeft, upperRight, lowerLeft, lowerRight;
+        point.x = xmin; point.y = ymax; upperLeft  = derivative(transform, point);
+        point.x = xmax; point.y = ymax; upperRight = derivative(transform, point);
+        point.x = xmin; point.y = ymin; lowerLeft  = derivative(transform, point);
+        point.x = xmax; point.y = ymin; lowerRight = derivative(transform, point);
+        /*
+         * The tolerance factor is scaled as below. This comment describes a one-dimensional
+         * case, but the two dimensional case works on the same principle.
+         *
+         * Let assume that we computed the derivative of y=f(x) at two locations: x₁ and
x₃.
+         * The derivative values (the slopes of the y=f(x) function) at those locations are
+         * m₁ and m₃.
+         *
+         *          /          _/
+         *         / x₁      _/ x₂      ─── x₃
+         *        / m₁=1    /  m₂≈½        m₃=0
+         *
+         * ResamplingGrid will interpolate the y values between x₁ and x₃. The interpolated
results
+         * should be exact at locations x₁ and x₃ and have some errors between those
two end points.
+         *
+         * HYPOTHESIS:
+         *  1) We presume that the greatest error will be located mid-way between x₁ and
x₃.
+         *     The x₂ point above represents that location.
+         *  2) We presume that the derivative between x₁ and x₃ varies continuously from
m₁ to m₃.
+         *     The derivative at x₂ may be something close to m₂ ≈ (m₁ + m₃) /
2, but we don't know for sure.
+         *
+         * Let compute linear approximations of y=f(x) using the two slopes m₁ and m₃.
If the hypothesis #2 is true,
+         * then the real y values are somewhere between the two approximations. The formulas
below uses the x₁ point,
+         * but we would get the same final equation if we used the x₃ instead (we don't
use both x₁ and x₃ since
+         * solving such equation produce 0=0).
+         *
+         * Given f₁(x) = y₁ + (x − x₁)⋅m₁
+         *   and f₃(x) = y₁ + (x − x₁)⋅m₃
+         *
+         * then the error ε = f₃(x) − f₁(x) at location x−x₂ is (x₂−x₁)⋅(m₃−m₁).
+         * Given x₂ = (x₁+x₃)/2, we get ε = (x₃−x₁)/2 ⋅ (m₃−m₁).
+         *
+         * If we rearange the terms, we get:  (m₃−m₁) = 2⋅ε / (x₃−x₁).
+         * The (m₃ − m₁) value is the maximal difference to be accepted
+         * in the coefficients of the derivative matrix to be compared.
+         */
+        final Dimension depth = depth(transform, point,
+                new Point2D.Double(2 * TOLERANCE / (xmax - xmin),
+                                   2 * TOLERANCE / (ymax - ymin)),
+                xmin, xmax, ymin, ymax, upperLeft, upperRight, lowerLeft, lowerRight);
+        if (depth.width == 0 && depth.height == 0) {
+            /*
+             * The transform is approximately affine. Compute the matrix coefficients using
the points projected
+             * on the four borders of the domain, in order to get a kind of average coefficient
values. We don't
+             * use the derivative matrix in the center location, because it may not be the
best "average" value
+             * and some map projection implementations use approximation derived from spherical
formulas.
+             * The difference is big enough for causing test failure.
+             */
+            final double xcnt = domain.getCenterX();
+            final double ycnt = domain.getCenterY();
+            double m00, m10, m01, m11;
+            Point2D p;
+            point.x=xmax; point.y=ycnt; p=transform.transform(point, point); m00  = p.getX();
m10  = p.getY();
+            point.x=xmin; point.y=ycnt; p=transform.transform(point, point); m00 -= p.getX();
m10 -= p.getY();
+            point.x=xcnt; point.y=ymax; p=transform.transform(point, point); m01  = p.getX();
m11  = p.getY();
+            point.x=xcnt; point.y=ymin; p=transform.transform(point, point); m01 -= p.getX();
m11 -= p.getY();
+            point.x=xcnt; point.y=ycnt; p=transform.transform(point, point);
+            final double width  = domain.getWidth();
+            final double height = domain.getHeight();
+            final AffineTransform tr = new AffineTransform(m00 / width,  m10 / width,
+                                                           m01 / height, m11 / height,
+                                                           p.getX(),     p.getY());
+            tr.translate(-xcnt, -ycnt);
+            roundIfAlmostInteger(tr);
+            return new AffineTransform2D(tr);
+        }
+        /*
+         * Non-affine transform. Create a grid using the cell size computed (indirectly)
+         * by the `depth(…)` method.
+         */
+        final int xStep     =  domain.width  / (1 << depth.width);
+        final int yStep     =  domain.height / (1 << depth.height);
+        final int xNumCells = (domain.width  + xStep-1) / xStep;
+        final int yNumCells = (domain.height + yStep-1) / yStep;
+        final double[] warpPositions = new double[2 * (xNumCells+1) * (yNumCells+1)];
+        final int xup = domain.x + xNumCells * xStep;
+        final int yup = domain.y + yNumCells * yStep;
+        int p = 0;
+        for (int y=domain.y; y <= yup; y += yStep) {
+            for (int x=domain.x; x <= xup; x += xStep) {
+                warpPositions[p++] = x + 0.5;
+                warpPositions[p++] = y + 0.5;
+            }
+        }
+        transform.transform(warpPositions, 0, warpPositions, 0, p/2);
+        return null;
+//        return new ResamplingGrid(domain.x, xStep, xNumCells,
+//                                  domain.y, yStep, yNumCells, warpPositions);
+    }
+
+    /**
+     * Computes the number of subdivisions (in power of 2) to apply in order to get a good
+     * {@link ResamplingGrid} approximation. The {@code width} and {@code height} fields
in
+     * the returned value have the following meaning:
+     *
+     * <ul>
+     *   <li>0 means that the transform is approximately affine in the region of interest.</li>
+     *   <li>1 means that we should split the grid in two parts horizontally and/or
vertically.</li>
+     *   <li>2 means that we should split the grid in four parts horizontally and/or
vertically.</li>
+     *   <li><i>etc.</i></li>
+     * </ul>
+     *
+     * @param  transform   the transform for which to compute the depth.
+     * @param  point       any {@code Point2D.Double} instance, to be written by this method.
+     *                     This is provided in argument only for reducing object allocations.
+     * @param  tolerance   the tolerance value to use in comparisons of matrix coefficients,
+     *                     along the X axis and along the Y axis. The distance between the
location
+     *                     of the matrix being compared is half the size of the region of
interest.
+     * @param  xmin        the minimal <var>x</var> ordinate.
+     * @param  xmax        the maximal <var>x</var> ordinate.
+     * @param  ymin        the minimal <var>y</var> ordinate.
+     * @param  ymax        the maximal <var>y</var> ordinate.
+     * @param  upperLeft   the transform derivative at {@code (xmin,ymax)}.
+     * @param  upperRight  the transform derivative at {@code (xmax,ymax)}.
+     * @param  lowerLeft   the transform derivative at {@code (xmin,ymin)}.
+     * @param  lowerRight  the transform derivative at {@code (xmax,ymin)}.
+     * @return the number of subdivision along each axis.
+     * @throws TransformException if a derivative can not be computed.
+     * @throws ImagingOpException if the grid would be too big for being useful.
+     */
+    private static Dimension depth(final MathTransform2D transform,
+                                   final Point2D.Double  point,
+                                   final Point2D.Double  tolerance,
+                                   final double xmin,       final double xmax,
+                                   final double ymin,       final double ymax,
+                                   final Matrix2 upperLeft, final Matrix2 upperRight,
+                                   final Matrix2 lowerLeft, final Matrix2 lowerRight)
+            throws TransformException
+    {
+        if (!(xmax - xmin >= MIN_SIZE) || !(ymax - ymin >= MIN_SIZE)) {           
     // Use ! for catching NaN.
+            throw new ImagingOpException(null);
+        }
+        /*
+         * All derivatives will be compared to the derivative at (centerX, centerY).
+         * Consequently, the distance between the derivatives are half the distance
+         * between [x|y]min and [x|y]max (approximately — we ignore the diagonal).
+         * Consequently, the tolerance threshold can be augmented by the same factor.
+         */
+        final double oldTolX = tolerance.x;
+        final double oldTolY = tolerance.y;
+        tolerance.x *= 2;
+        tolerance.y *= 2;
+        final double centerX = point.x = 0.5 * (xmin + xmax);
+        final double centerY = point.y = 0.5 * (ymin + ymax);
+        final Matrix2 center = Matrix2.castOrCopy(transform.derivative(point));
+        point.x = xmin;    point.y = centerY; final Matrix2 centerLeft  = derivative(transform,
point);
+        point.x = xmax;    point.y = centerY; final Matrix2 centerRight = derivative(transform,
point);
+        point.x = centerX; point.y = ymin;    final Matrix2 centerLower = derivative(transform,
point);
+        point.x = centerX; point.y = ymax;    final Matrix2 centerUpper = derivative(transform,
point);
+        final boolean cl = equals(center, centerLeft,  tolerance);
+        final boolean cr = equals(center, centerRight, tolerance);
+        final boolean cb = equals(center, centerLower, tolerance);
+        final boolean cu = equals(center, centerUpper, tolerance);
+        int nx=0, ny=0;
+        /*
+         *   upperLeft  ┌──────┬─ centerUpper
+         *              │      │
+         *   centerLeft ├──────┼─ center
+         */
+        if (!((cl & cu) && equals(center, upperLeft, tolerance))) {
+            final Dimension depth = depth(transform, point, tolerance, xmin, centerX, centerY,
ymax,
+                                          upperLeft, centerUpper, centerLeft, center);
+            incrementNonAffineDimension(cl, cu, depth);
+            nx = depth.width;
+            ny = depth.height;
+        }
+        /*
+         *   centerUpper ─┬──────┐ upperRight
+         *                │      │
+         *   center      ─┼──────┤ centerRight
+         */
+        if (!((cr & cu) && equals(center, upperRight, tolerance))) {
+            final Dimension depth = depth(transform, point, tolerance, centerX, xmax, centerY,
ymax,
+                                          centerUpper, upperRight, center, centerRight);
+            incrementNonAffineDimension(cr, cu, depth);
+            nx = Math.max(nx, depth.width);
+            ny = Math.max(ny, depth.height);
+        }
+        /*
+         *   centerLeft ├──────┼─ center
+         *              │      │
+         *   lowerLeft  └──────┴─ centerLower
+         */
+        if (!((cl & cb) && equals(center, lowerLeft, tolerance))) {
+            final Dimension depth = depth(transform, point, tolerance, xmin, centerX, ymin,
centerY,
+                                          centerLeft, center, lowerLeft, centerLower);
+            incrementNonAffineDimension(cl, cb, depth);
+            nx = Math.max(nx, depth.width);
+            ny = Math.max(ny, depth.height);
+        }
+        /*
+         *   center      ─┼──────┤ centerRight
+         *                │      │
+         *   centerLower ─┴──────┘ lowerRight
+         */
+        if (!((cr & cb) && equals(center, lowerRight, tolerance))) {
+            final Dimension depth = depth(transform, point, tolerance, centerX, xmax, ymin,
centerY,
+                                          center, centerRight, centerLower, lowerRight);
+            incrementNonAffineDimension(cr, cb, depth);
+            nx = Math.max(nx, depth.width);
+            ny = Math.max(ny, depth.height);
+        }
+        tolerance.x = oldTolX;
+        tolerance.y = oldTolY;
+        return new Dimension(nx, ny);
+    }
+
+    /**
+     * Increments the width, the height or both values in the given dimension, depending
on which
+     * dimension are not affine. This method <strong>must</strong> be invoked
using the following
+     * pattern, where {@code center} is the matrix of the transform derivative in the center
of
+     * the region of interest. Note: the order of operations in the {@code if} statement
matter!
+     *
+     * {@code java
+     *     he  =  center.equals(matrixOnTheSameHorizontalLine, tolerance);
+     *     ve  =  center.equals(matrixOnTheSameVerticalLine,   tolerance);
+     *     if (!((he & ve) && center.equals(matrixOnADiagonal, tolerance))) {
+     *         incrementNonAffineDimension(he, ve, depth);
+     *     }
+     * }
+     *
+     * @param he    {@code true} if the matrix on the horizontal line are equal.
+     * @param ve    {@code true} if the matrix on the vertical line are equal.
+     * @param depth the dimension in which to increment the width, height or both.
+     */
+    private static void incrementNonAffineDimension(boolean he, boolean ve, Dimension depth)
{
+        if (he == ve) {
+            /*
+             * Both dimensions are not affine: either (he,ve) == false (the obvious case),
+             * or (he,ve) == true in which case this method has been invoked only if the
+             * last `center.equals(…)` test in the `if` statement returned false.
+             */
+            depth.width++;
+            depth.height++;
+        } else if (ve) {
+            // Implies (he == false): horizontal dimension is not affine.
+            // Don't touch to the vertical dimension since it is affine.
+            depth.width++;
+        } else {
+            // Implies (he == true): horizontal dimension is affine, don't touch it.
+            depth.height++;
+        }
+    }
+
+    /**
+     * Computes the derivative of the given transform at the given location and returns the
result as a 2×2 matrix.
+     * This method invokes the {@link MathTransform2D#derivative(Point2D)} and converts or
casts the result to a
+     * {@link Matrix2} instance.
+     *
+     * <p>In Apache SIS implementations, matrices returned by {@code derivative(Point2D)}
methods are already
+     * instances of {@link Matrix2}. Consequently in most cases this method will just cast
the result.</p>
+     *
+     * @param  transform  the transform for which to compute the derivative.
+     * @param  point      the location where to compute the derivative.
+     * @return the derivative at the given location as a 2×2 matrix.
+     * @throws TransformException if the derivative can not be computed.
+     */
+    private static Matrix2 derivative(final MathTransform2D transform, final Point2D point)
throws TransformException {
+        return Matrix2.castOrCopy(transform.derivative(point));
+    }
+
+    /**
+     * Returns {@code true} if the given matrices are equal, up to the given tolerance thresholds.
+     * The thresholds can be different for the X and Y axes. This allows to break the loop
sooner
+     * (resulting in smaller grids) inside the {@link #depth depth(…)} method.
+     */
+    private static boolean equals(final Matrix2 center, final Matrix2 corner, final Point2D.Double
tolerance) {
+        return abs(center.m00 - corner.m00) <= tolerance.x &&
+               abs(center.m01 - corner.m01) <= tolerance.x &&
+               abs(center.m10 - corner.m10) <= tolerance.y &&
+               abs(center.m11 - corner.m11) <= tolerance.y;
+    }
+
+    /**
+     * If scale and shear coefficients are close to integers, replaces their current values
by their rounded values.
+     * The scale and shear coefficients are handled in a "all or nothing" way; either all
of them or none are rounded.
+     * The translation terms are handled separately, provided that the scale and shear coefficients
have been rounded.
+     *
+     * @param  tr  the transform to round. Rounding will be applied in place.
+     */
+    static void roundIfAlmostInteger(final AffineTransform tr) {
+        double r;
+        final double m00, m01, m10, m11;
+        if (abs((m00 = rint(r=tr.getScaleX())) - r) <= EPS &&
+            abs((m01 = rint(r=tr.getShearX())) - r) <= EPS &&
+            abs((m11 = rint(r=tr.getScaleY())) - r) <= EPS &&
+            abs((m10 = rint(r=tr.getShearY())) - r) <= EPS)
+        {
+            /*
+             * At this point the scale and shear coefficients can been rounded to integers.
+             * Continue only if this rounding does not make the transform non-invertible.
+             */
+            if ((m00!=0 || m01!=0) && (m10!=0 || m11!=0)) {
+                double m02, m12;
+                if (abs((r = rint(m02=tr.getTranslateX())) - m02) <= EPS) m02=r;
+                if (abs((r = rint(m12=tr.getTranslateY())) - m12) <= EPS) m12=r;
+                tr.setTransform(m00, m10, m01, m11, m02, m12);
+            }
+        }
+    }
+}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/ResamplingGridTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/ResamplingGridTest.java
new file mode 100644
index 0000000..7420b00
--- /dev/null
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/ResamplingGridTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.geom.AffineTransform;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests {@link ResamplingGrid}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public final strictfp class ResamplingGridTest extends TestCase {
+    /**
+     * Tests the {@link ResamplingGrid#roundIfAlmostInteger(AffineTransform)} method.
+     */
+    @Test
+    public void testRoundIfAlmostInteger() {
+        final AffineTransform test = new AffineTransform(4, 0, 0, 4, -400, -1186);
+        final AffineTransform copy = new AffineTransform(test);
+        ResamplingGrid.roundIfAlmostInteger(test);
+        assertEquals("Translation terms were already integers, so the " +
+                "transform should not have been modified.", copy, test);
+
+        test.translate(ResamplingGrid.EPS/8, -ResamplingGrid.EPS/8);
+        ResamplingGrid.roundIfAlmostInteger(test);
+        assertEquals("Translation terms should have been rounded.", copy, test);
+
+        test.translate(ResamplingGrid.EPS*2, -ResamplingGrid.EPS*2);
+        ResamplingGrid.roundIfAlmostInteger(test);
+        assertFalse("Treshold was smaller than the translation, so the " +
+                "transform should not have been modified.", copy.equals(test));
+    }
+}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
index e95bec9..63577fe 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
@@ -81,6 +81,7 @@ import org.junit.runners.Suite;
     org.apache.sis.image.DefaultIteratorTest.class,
     org.apache.sis.image.LinearIteratorTest.class,
     org.apache.sis.image.StatisticsCalculatorTest.class,
+    org.apache.sis.image.ResamplingGridTest.class,
     org.apache.sis.image.ResampledImageTest.class,
     org.apache.sis.coverage.CategoryTest.class,
     org.apache.sis.coverage.CategoryListTest.class,


Mime
View raw message