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
The following commit(s) were added to refs/heads/feat/image2polygon by this
push:
new acf0d9371e Rework ImageProcessor areas operation, use Predicate and
DoubleToIntFunction to classify points
acf0d9371e is described below
commit acf0d9371eb38f164f81aa55fae99a54eb829f3e
Author: jsorel <[email protected]>
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 1345b4075d..fc2afe745e 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());
- }
- }
-
}