This is an automated email from the ASF dual-hosted git repository.

jsorel pushed a commit to branch feat/image2polygon
in repository https://gitbox.apache.org/repos/asf/sis.git

commit fd950b7257076eafbd0546adce7033ee8fa2ca57
Author: jsorel <johann.so...@geomatys.com>
AuthorDate: Tue Mar 25 16:57:26 2025 +0100

    Rework ImageProcessor areas operation, use Predicate and 
DoubleToIntFunction to classify points
---
 .../main/org/apache/sis/image/ImageProcessor.java  |  56 +++++++---
 .../apache/sis/image/processing/polygon/Block.java |   6 +-
 .../sis/image/processing/polygon/Boundary.java     |   8 +-
 .../sis/image/processing/polygon/Polygonize.java   | 122 ++++++++-------------
 4 files changed, 94 insertions(+), 98 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java
index 9ab54631c9..cb3c52f313 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java
@@ -34,6 +34,9 @@ import java.awt.image.RenderedImage;
 import java.awt.image.ImagingOpException;
 import java.awt.image.IndexColorModel;
 import java.awt.image.WritableRenderedImage;
+import java.util.ArrayList;
+import java.util.function.DoubleToIntFunction;
+import java.util.function.Predicate;
 import javax.measure.Quantity;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransform1D;
@@ -1543,32 +1546,55 @@ public class ImageProcessor implements Cloneable {
     }
 
     /**
-     * Generates area polygons at the specified ranges computed from data 
provided by the given image.
+     * Generates area polygons by grouping samples matching given predicate 
computed from data provided by the given image.
      * Polygons will be computed for every bands in the given image.
-     * For each band, the result is given as a {@code Map} where keys are the 
specified {@code ranges}
-     * and values are the polygons at the associated range.
-     * If there are no polygons for a given level, there will be no 
corresponding entry in the map.
-     * The provided {@code ranges} must not overlap each other.
+     * For each band, the result is given as a {@code List} if polygon 
matching the predicate.
      *
      * @param  data       image providing source values.
-     * @param  ranges    value ranges for which to compute polygones. An array 
should be provided for each band.
-     *                    If there is more bands than {@code ranges.length}, 
the last array is reused for
-     *                    all remaining bands.
+     * @param  predicates  predicate to indicate if a value is to be included 
in the shape
+     * @param  gridToCRS  transform from pixel coordinates to geometry 
coordinates, or {@code null} if none.
+     *                    Integer source coordinates are located at pixel 
centers.
+     * @return the polygons of samples matching the predicate. The {@code 
List} size is the number of bands.
+     *         List values are the polygons as a Java2D {@link Shape}.
+     * @throws ImagingOpException if an error occurred during calculation.
+     */
+    public List<List<Shape>> areas(final RenderedImage data, 
Predicate<Double>[] predicates, final MathTransform gridToCRS) throws 
TransformException {
+        final DoubleToIntFunction[] array = new 
DoubleToIntFunction[predicates.length];
+        for (int i = 0; i < predicates.length; i++) {
+            final Predicate<Double> predicate = predicates[i];
+            array[i] = (double value) -> predicate.test(value) ? 1 : 0;
+        }
+        final List<Map<Integer, List<Shape>>> result = areas(data, array, 
gridToCRS);
+        final List<List<Shape>> results = new ArrayList<>();
+        for (Map<Integer, List<Shape>> map : result) {
+            List<Shape> lst = map.get(1);
+            if (lst == null) lst = new ArrayList<>();
+            results.add(lst);
+        }
+        return results;
+    }
+    
+    /**
+     * Generates area polygons by grouping samples in the same classification 
computed from data provided by the given image.
+     * Polygons will be computed for every bands in the given image.
+     * For each band, the result is given as a {@code Map} where keys are the 
classifiers returned values.
+     *
+     * @param  data       image providing source values.
+     * @param  classifiers generate a classification key for a sample values, 
those values are used as key in the returned map
      * @param  gridToCRS  transform from pixel coordinates to geometry 
coordinates, or {@code null} if none.
      *                    Integer source coordinates are located at pixel 
centers.
-     * @return the polygons for specified ranges in each band. The {@code 
List} size is the number of bands.
-     *         For each band, the {@code Map} size is equal or less than 
{@code ranges[band].length}.
-     *         Map keys are the specified ranges, excluding those for which 
there are no polygons.
+     * @return the polygons for specified classification keys in each band. 
The {@code List} size is the number of bands.
+     *         Map keys are the returned keys from the classifiers.
      *         Map values are the polygons as a Java2D {@link Shape}.
      * @throws ImagingOpException if an error occurred during calculation.
      */
-    public List<Map<NumberRange,List<Shape>>> areas(final RenderedImage data, 
NumberRange[][] ranges, final MathTransform gridToCRS) throws 
TransformException {
-        final Polygonize polygonizer = new Polygonize(data, ranges);
-        final List<Map<NumberRange, List<Shape>>> result = 
polygonizer.polygones();
+    public List<Map<Integer,List<Shape>>> areas(final RenderedImage data, 
DoubleToIntFunction[] classifiers, final MathTransform gridToCRS) throws 
TransformException {
+        final Polygonize polygonizer = new Polygonize(data, classifiers);
+        final List<Map<Integer, List<Shape>>> result = polygonizer.polygones();
         
         if (gridToCRS != null && !gridToCRS.isIdentity()) {
             final MathTransform2D trs2d = 
MathTransforms.bidimensional(gridToCRS);
-            for (Map<NumberRange, List<Shape>> m : result) {
+            for (Map<Integer, List<Shape>> m : result) {
                 for (List<Shape> lst : m.values()) {
                     for (int i = 0, n = lst.size(); i < n; i++) {
                         lst.set(i, trs2d.createTransformedShape(lst.get(i)));
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Block.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Block.java
index 45cbea909d..db67d850e7 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Block.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Block.java
@@ -16,8 +16,6 @@
  */
 package org.apache.sis.image.processing.polygon;
 
-import org.apache.sis.measure.NumberRange;
-
 /**
  * Define a group of pixels with the same range.
  *
@@ -25,14 +23,14 @@ import org.apache.sis.measure.NumberRange;
  */
 final class Block {
 
-    public NumberRange range;
+    public int classe;
     public int startX;
     public int endX;
     public int y;
     public Boundary boundary;
 
     public void reset(){
-        range = null;
+        classe = -1;
         startX = -1;
         endX = -1;
         y = -1;
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Boundary.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Boundary.java
index 5a4ce953e9..b89179a594 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Boundary.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Boundary.java
@@ -40,10 +40,10 @@ final class Boundary {
 
     //in construction geometries
     private final LinkedList<LinkedList<Point2D.Double>> floatings = new 
LinkedList<LinkedList<Point2D.Double>>();
-    final NumberRange range;
+    final int classe;
 
-    public Boundary(final NumberRange range){
-        this.range = range;
+    public Boundary(final int classe){
+        this.classe = classe;
     }
 
     public void start(final int firstX, final int secondX, final int y){
@@ -329,7 +329,7 @@ final class Boundary {
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("Boundary : ");
-        sb.append(range.toString());
+        sb.append(classe);
 
         for(LinkedList<Point2D.Double> coords : floatings){
             sb.append("  \t{");
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Polygonize.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Polygonize.java
index 1782f30357..9c1a62f341 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Polygonize.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Polygonize.java
@@ -24,7 +24,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import org.apache.sis.measure.NumberRange;
+import java.util.function.DoubleToIntFunction;
 import org.apache.sis.image.PixelIterator;
 import org.opengis.coverage.grid.SequenceType;
 
@@ -40,7 +40,7 @@ public final class Polygonize {
     private static final int CURRENT_LINE = 1;
 
     //last line cache boundary
-    private final List<Map<NumberRange, List<Shape>>> polygons = new 
ArrayList<>();
+    private final List<Map<Integer, List<Shape>>> polygons = new ArrayList<>();
 
     //buffer[band][LAST_LINE] holds last line buffer
     //buffer[band][CURRENT_LINE] holds current line buffer
@@ -50,7 +50,7 @@ public final class Polygonize {
     private Block[] blocks;
 
     private final RenderedImage image;
-    private final NumberRange[][] ranges;
+    private final DoubleToIntFunction[] classifiers;
     
     /**
      *
@@ -58,32 +58,25 @@ public final class Polygonize {
      * @param ranges data value ranges
      * @param band coverage band to process
      */
-    public Polygonize(RenderedImage image, NumberRange[][] ranges){
+    public Polygonize(RenderedImage image, DoubleToIntFunction[] classifiers){
         this.image = image;
-        this.ranges = ranges;
+        this.classifiers = classifiers;
     }
     
-    public List<Map<NumberRange, List<Shape>>> polygones() {
+    public List<Map<Integer, List<Shape>>> polygones() {
 
         final PixelIterator iter = new 
PixelIterator.Builder().setIteratorOrder(SequenceType.LINEAR).create(image);
         final int nbBand = iter.getNumBands();
         blocks = new Block[nbBand];
         
-        final NumberRange NaNRange = new NaNRange();
+        final DoubleToIntFunction[] classifiers = new 
DoubleToIntFunction[nbBand];
         for (int band = 0; band < nbBand; band++) {            
-            final Map<NumberRange, List<Shape>> bandState = new HashMap<>();   
         
-            //add a range for Nan values.            
-            bandState.put(NaNRange, new ArrayList<>());
-            polygons.add(bandState);
-
-            for (final NumberRange range : ranges[Math.min(band, 
ranges.length-1)]) {
-                bandState.put(range, new ArrayList<>());
-            }
-            
+            final Map<Integer, List<Shape>> bandState = new HashMap<>();
+            polygons.add(bandState);            
             blocks[band] = new Block();
+            classifiers[band] = 
this.classifiers[Math.max(this.classifiers.length-1, band)];
         }
         
-        
         /*
          This algorithm create polygons which follow the contour of each pixel.
          The 0,0 coordinate will match the pixel corner.
@@ -102,7 +95,7 @@ public final class Polygonize {
                 gridPosition.x = x;
                 pixel = iter.getPixel(pixel);
                 for (int band = 0; band < nbBand; band++) {
-                    append(polygons.get(band), buffers[band], blocks[band], 
gridPosition, pixel[band]);
+                    append(classifiers[band], polygons.get(band), 
buffers[band], blocks[band], gridPosition, pixel[band]);
                 }
                 iter.next();
             }
@@ -130,58 +123,51 @@ public final class Polygonize {
                         new Point2D.Double(i+1, gridPosition.y)
                         );
                 if (poly != null) {
-                    
polygons.get(band).get(buffers[band][LAST_LINE][i].range).add(poly);
+                    final int classe = buffers[band][LAST_LINE][i].classe;
+                    final Map<Integer, List<Shape>> map = polygons.get(band);
+                    List<Shape> lst = map.get(classe);
+                    if (lst == null) {
+                        lst = new ArrayList<>();
+                        map.put(classe, lst);
+                    }
+                    lst.add(poly);
                 }
             }
         }
         
         //avoid memory use
         buffers = null;
-        final List<Map<NumberRange, List<Shape>>> copy = new 
ArrayList<>(polygons);
-        //remove the NaNRange
-        for (Map m : copy) {
-            m.remove(NaNRange);
-        }
+        final List<Map<Integer, List<Shape>>> copy = new ArrayList<>(polygons);
         polygons.clear();        
         return copy;
     }
 
-    private static void append(Map<NumberRange, List<Shape>> results, final 
Boundary[][] buffers, final Block block, final Point point, Number value) {
-
-        //special case for NaN or null
-        final NumberRange valueRange;
-        if (value == null || Double.isNaN(value.doubleValue())) {
-            valueRange = new NaNRange();
-        } else {
-            valueRange = results.keySet().stream()
-                    .filter(range -> range.containsAny(value))
-                    .findAny()
-                    .orElseThrow(() -> new IllegalArgumentException("Value not 
in any range :" + value));
-        }
+    private static void append(DoubleToIntFunction classifier, Map<Integer, 
List<Shape>> results, final Boundary[][] buffers, final Block block, final 
Point point, Number value) {
 
-        if (valueRange.equals(block.range)) {
-            //last pixel was in the same range
+        final int classe = classifier.applyAsInt(value.doubleValue());
+        if (classe == block.classe) {
+            //last pixel was in the same class
             block.endX = point.x;
             return;
-        } else if (block.range != null) {
+        } else if (block.classe != -1) {
             //last pixel was in a different range, save it's geometry
             constructBlock(results, block, buffers);
         }
 
         //start a pixel serie
-        block.range = valueRange;
+        block.classe = classe;
         block.startX = point.x;
         block.endX = point.x;
         block.y = point.y;
     }
 
-    private static void constructBlock(Map<NumberRange, List<Shape>> results, 
final Block block, final Boundary[][] buffers) {
+    private static void constructBlock(Map<Integer, List<Shape>> results, 
final Block block, final Boundary[][] buffers) {
 
         //System.err.println("BLOCK ["+block.startX+","+block.endX+"]");
 
         if(block.y == 0) {
             //first line, the buffer is empty, must fill it
-            final Boundary boundary = new Boundary(block.range);
+            final Boundary boundary = new Boundary(block.classe);
             boundary.start(block.startX, block.endX+1, block.y);
 
             for(int i=block.startX; i<=block.endX; i++) {
@@ -196,7 +182,7 @@ public final class Polygonize {
                 final int[] candidateExtent = findExtent(buffers, i);
 
                 //do not treat same blockes here
-                if (candidate.range != block.range) {
+                if (candidate.classe != block.classe) {
                     //System.err.println("A different block extent : "+ 
candidateExtent[0] + " " + candidateExtent[1]);
                     //System.err.println("before :" + candidate.toString());
 
@@ -206,13 +192,27 @@ public final class Polygonize {
                                 new Point2D.Double(candidateExtent[0], 
block.y),
                                 new Point2D.Double(candidateExtent[1]+1, 
block.y)
                                 );
-                        if(poly != null) 
results.get(candidate.range).add(poly);
+                        if (poly != null) {
+                            List<Shape> lst = results.get(candidate.classe);
+                            if (lst == null) {
+                                lst = new ArrayList<>();
+                                results.put(candidate.classe, lst);
+                            }
+                            lst.add(poly);
+                        }
                     } else {
                         final Shape poly = candidate.link(
                                 new Point2D.Double( 
(block.startX<candidateExtent[0]) ? candidateExtent[0]: block.startX, block.y),
                                 new Point2D.Double( 
(block.endX>candidateExtent[1]) ? candidateExtent[1]+1: block.endX+1, block.y)
                                 );
-                        if (poly != null) 
results.get(candidate.range).add(poly);
+                        if (poly != null) {
+                            List<Shape> lst = results.get(candidate.classe);
+                            if (lst == null) {
+                                lst = new ArrayList<>();
+                                results.put(candidate.classe, lst);
+                            }
+                            lst.add(poly);
+                        }
                     }
 
                     //System.err.println("after :" + candidate.toString());
@@ -232,9 +232,7 @@ public final class Polygonize {
                 final int[] candidateExtent = findExtent(buffers, i);
 
                 //do not treat different blocks here
-                if (candidate.range == block.range) {
-                    //System.err.println("A firnet block extent : "+ 
candidateExtent[0] + " " + candidateExtent[1]);
-//                    //System.err.println("before :" + candidate.toString());
+                if (candidate.classe == block.classe) {
 
                     if (currentBoundary == null) {
                         //set the current boundary, will expend this one
@@ -250,7 +248,6 @@ public final class Polygonize {
                             );
 
                         replaceInLastLigne(buffers, candidate, 
currentBoundary);
-                        //System.out.println("Merging : " + 
currentBoundary.toString());
                     }
 
                     if (candidateExtent[0] < firstAnchor) {
@@ -262,14 +259,10 @@ public final class Polygonize {
                 i = candidateExtent[1]+1;
             }
 
-            if (currentBoundary != null) {
-                //System.err.println("before :" + currentBoundary.toString());
-            }
-
             if (currentBoundary == null) {
                 //no previous friendly boundary to link with
                 //make a new one
-                currentBoundary = new Boundary(block.range);
+                currentBoundary = new Boundary(block.classe);
                 currentBoundary.start(block.startX, block.endX+1, block.y);
             } else {
                 if (firstAnchor < block.startX) {
@@ -278,7 +271,6 @@ public final class Polygonize {
                 }
 
                 //add the coordinates
-                //System.err.println("> first anchor : " +firstAnchor + " 
lastAnchor : " +lastAnchor);
                 if (firstAnchor == block.startX) {
                     currentBoundary.add(
                         new Point2D.Double(firstAnchor, block.y),
@@ -302,17 +294,14 @@ public final class Polygonize {
                             new Point2D.Double(block.endX+1, block.y+1)
                             );
                     } else {
-                        //System.err.println("0 add :" + 
currentBoundary.toString());
                         currentBoundary.add(
                             new Point2D.Double(lastAnchor, block.y),
                             new Point2D.Double(block.endX+1, block.y)
                             );
-                        //System.err.println("1 add:" + 
currentBoundary.toString());
                         currentBoundary.add(
                             new Point2D.Double(block.endX+1, block.y),
                             new Point2D.Double(block.endX+1, block.y+1)
                             );
-                        //System.err.println("after add:" + 
currentBoundary.toString());
                     }
                 } else {
                     currentBoundary.addFloating(
@@ -320,13 +309,8 @@ public final class Polygonize {
                             new Point2D.Double(block.endX+1, block.y+1)
                             );
                 }
-
-                //System.err.println(currentBoundary.toString());
-
             }
 
-            //System.err.println("after :" + currentBoundary.toString());
-
             //fill in the current line 
-----------------------------------------
 
             for (int i = block.startX; i <= block.endX; i++) {
@@ -369,16 +353,4 @@ public final class Polygonize {
         return extent;
     }
 
-    private static class NaNRange extends NumberRange{
-
-        public NaNRange() {
-            super(Double.class, 0d, true, 0d, true);
-        }
-
-        @Override
-        public boolean contains(final Comparable number) throws 
IllegalArgumentException {
-            return Double.isNaN(((Number) number).doubleValue());
-        }
-    }
-
 }

Reply via email to