From 4ee05287c32b7e6b69df13716ff06983cb871aab Mon Sep 17 00:00:00 2001
From: tpietzsch
Date: Wed, 9 Nov 2022 21:55:22 +0100
Subject: [PATCH 01/13] Add static ListImg.wrap() constructor method that wraps
an existing List
---
.../java/net/imglib2/img/list/ListImg.java | 20 ++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/src/main/java/net/imglib2/img/list/ListImg.java b/src/main/java/net/imglib2/img/list/ListImg.java
index f3187ebdb..024e753bf 100644
--- a/src/main/java/net/imglib2/img/list/ListImg.java
+++ b/src/main/java/net/imglib2/img/list/ListImg.java
@@ -37,6 +37,7 @@
import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
import net.imglib2.img.Img;
import net.imglib2.type.Type;
@@ -61,7 +62,7 @@
*/
public class ListImg< T > extends AbstractListImg< T >
{
- final private ArrayList< T > pixels;
+ final private List< T > pixels;
public ListImg( final long[] dim, final T type )
{
@@ -86,9 +87,9 @@ public ListImg( final long[] dim, final T type )
public ListImg( final Collection< T > collection, final long... dim )
{
super( dim );
-
+
assert numPixels == collection.size() : "Dimensions do not match number of pixels.";
-
+
pixels = new ArrayList< T >( ( int ) numPixels );
pixels.addAll( collection );
}
@@ -130,4 +131,17 @@ public ListImg< T > copy()
}
return new ListImg< T >( this.pixels, dimension );
}
+
+ private ListImg( final List< T > pixels, final boolean dummy, final long... dim )
+ {
+ super( dim );
+
+ assert numPixels == pixels.size() : "Dimensions do not match number of pixels.";
+ this.pixels = pixels;
+ }
+
+ public static < T > ListImg< T > wrap( List< T > pixels, final long... dim )
+ {
+ return new ListImg<>( pixels, false, dim );
+ }
}
From 8657f304e469ed560f3cabfb2e61768c0430ea7f Mon Sep 17 00:00:00 2001
From: tpietzsch
Date: Sat, 19 Nov 2022 22:09:55 +0100
Subject: [PATCH 02/13] Add default getDistance() implementation for neighbor
search interfaces
---
.../net/imglib2/neighborsearch/KNearestNeighborSearch.java | 5 ++++-
.../net/imglib2/neighborsearch/NearestNeighborSearch.java | 5 ++++-
.../net/imglib2/neighborsearch/RadiusNeighborSearch.java | 5 ++++-
3 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/main/java/net/imglib2/neighborsearch/KNearestNeighborSearch.java b/src/main/java/net/imglib2/neighborsearch/KNearestNeighborSearch.java
index 781253fb6..edb3f6c8e 100644
--- a/src/main/java/net/imglib2/neighborsearch/KNearestNeighborSearch.java
+++ b/src/main/java/net/imglib2/neighborsearch/KNearestNeighborSearch.java
@@ -89,7 +89,10 @@ public interface KNearestNeighborSearch< T > extends NearestNeighborSearch< T >
* the last search and the ith nearest neighbor, ordered
* by square Euclidean distance.
*/
- double getDistance( int i );
+ default double getDistance( final int i )
+ {
+ return Math.sqrt( getSquareDistance( i ) );
+ }
/**
* Create a copy.
diff --git a/src/main/java/net/imglib2/neighborsearch/NearestNeighborSearch.java b/src/main/java/net/imglib2/neighborsearch/NearestNeighborSearch.java
index 43d02daea..e2888df56 100644
--- a/src/main/java/net/imglib2/neighborsearch/NearestNeighborSearch.java
+++ b/src/main/java/net/imglib2/neighborsearch/NearestNeighborSearch.java
@@ -81,7 +81,10 @@ public interface NearestNeighborSearch< T > extends EuclideanSpace
* the last search and the nearest neighbor, ordered by square Euclidean
* distance.
*/
- double getDistance();
+ default double getDistance()
+ {
+ return Math.sqrt( getSquareDistance() );
+ }
/**
* Create a copy.
diff --git a/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearch.java b/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearch.java
index d55689753..db7d6913d 100644
--- a/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearch.java
+++ b/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearch.java
@@ -106,5 +106,8 @@ public interface RadiusNeighborSearch< T > extends EuclideanSpace
* Access the Euclidean distance between the reference location as used for
* the last search and the ith neighbor.
*/
- double getDistance( int i );
+ default double getDistance( final int i )
+ {
+ return Math.sqrt( getSquareDistance( i ) );
+ }
}
From cdfbb073468325100907f20def4570e721173f52 Mon Sep 17 00:00:00 2001
From: tpietzsch
Date: Wed, 18 Jan 2023 13:23:15 +0100
Subject: [PATCH 03/13] Add RadiusNeighborSearch.copy() method
for consistency, because the other NeighborSearch interfaces have copy()
methods
---
.../net/imglib2/neighborsearch/RadiusNeighborSearch.java | 5 +++++
.../neighborsearch/RadiusNeighborSearchOnKDTree.java | 9 +++++++++
2 files changed, 14 insertions(+)
diff --git a/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearch.java b/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearch.java
index db7d6913d..02aa94df8 100644
--- a/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearch.java
+++ b/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearch.java
@@ -110,4 +110,9 @@ default double getDistance( final int i )
{
return Math.sqrt( getSquareDistance( i ) );
}
+
+ /**
+ * Create a copy.
+ */
+ RadiusNeighborSearch< T > copy();
}
diff --git a/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearchOnKDTree.java b/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearchOnKDTree.java
index 28e00b450..909b5402e 100644
--- a/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearchOnKDTree.java
+++ b/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearchOnKDTree.java
@@ -146,4 +146,13 @@ public double getDistance( final int i )
{
return Math.sqrt( resultPoints.get( i ).b );
}
+
+ @Override
+ public RadiusNeighborSearchOnKDTree< T > copy()
+ {
+ final RadiusNeighborSearchOnKDTree< T > copy = new RadiusNeighborSearchOnKDTree<>( tree );
+ System.arraycopy( pos, 0, copy.pos, 0, pos.length );
+ copy.resultPoints.addAll( resultPoints );
+ return copy;
+ }
}
From 0dbb9b6be677c005947f01c8b516d826a5b0fa20 Mon Sep 17 00:00:00 2001
From: tpietzsch
Date: Fri, 14 Jul 2023 14:24:36 +0200
Subject: [PATCH 04/13] Revise KDTree implementation
---
src/main/java/net/imglib2/KDTree.java | 741 ++++--------------
src/main/java/net/imglib2/KDTreeNode.java | 185 ++---
.../java/net/imglib2/kdtree/KDTreeData.java | 323 ++++++++
.../java/net/imglib2/kdtree/KDTreeImpl.java | 229 ++++++
.../java/net/imglib2/kdtree/KDTreeUtils.java | 573 ++++++++++++++
.../kdtree/KNearestNeighborSearchImpl.java | 125 +++
.../kdtree/NearestNeighborSearchImpl.java | 95 +++
.../kdtree/RadiusNeighborSearchImpl.java | 155 ++++
.../KNearestNeighborSearchOnKDTree.java | 102 +--
.../NearestNeighborSearchOnKDTree.java | 73 +-
.../RadiusNeighborSearchOnKDTree.java | 102 +--
.../net/imglib2/kdtree/KDTreeBenchmark.java | 186 +++++
.../net/imglib2/kdtree/KDTreeImplTest.java | 151 ++++
13 files changed, 2144 insertions(+), 896 deletions(-)
create mode 100644 src/main/java/net/imglib2/kdtree/KDTreeData.java
create mode 100644 src/main/java/net/imglib2/kdtree/KDTreeImpl.java
create mode 100644 src/main/java/net/imglib2/kdtree/KDTreeUtils.java
create mode 100644 src/main/java/net/imglib2/kdtree/KNearestNeighborSearchImpl.java
create mode 100644 src/main/java/net/imglib2/kdtree/NearestNeighborSearchImpl.java
create mode 100644 src/main/java/net/imglib2/kdtree/RadiusNeighborSearchImpl.java
create mode 100644 src/test/java/net/imglib2/kdtree/KDTreeBenchmark.java
create mode 100644 src/test/java/net/imglib2/kdtree/KDTreeImplTest.java
diff --git a/src/main/java/net/imglib2/KDTree.java b/src/main/java/net/imglib2/KDTree.java
index 46bd1da70..6a95e53b8 100644
--- a/src/main/java/net/imglib2/KDTree.java
+++ b/src/main/java/net/imglib2/KDTree.java
@@ -1,177 +1,35 @@
-/*
- * #%L
- * ImgLib2: a general-purpose, multidimensional image processing library.
- * %%
- * Copyright (C) 2009 - 2023 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld,
- * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke,
- * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner,
- * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert,
- * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin,
- * Jean-Yves Tinevez and Michael Zinsmaier.
- * %%
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- * #L%
- */
-
package net.imglib2;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Comparator;
import java.util.List;
-import java.util.ListIterator;
-
-import net.imglib2.util.KthElement;
-
-/**
- * KDTree to access values at RealLocalizable positions.
- *
- * @param
- * type of values stored in the tree.
- *
- * @author Tobias Pietzsch
- */
-public class KDTree< T > implements EuclideanSpace, IterableRealInterval< T >
-{
- /**
- * the number of dimensions.
- */
- final protected int n;
-
- final protected KDTreeNode< T > root;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+import net.imglib2.converter.AbstractConvertedIterableRealInterval;
+import net.imglib2.converter.AbstractConvertedRealCursor;
+import net.imglib2.kdtree.KDTreeData;
+import net.imglib2.kdtree.KDTreeImpl;
- /**
- * the number of nodes in the tree.
- */
- final protected long size;
+import static net.imglib2.kdtree.KDTreeData.PositionsLayout.FLAT;
- /**
- * minimum of each dimension.
- */
- final protected double[] min;
+public class KDTree< T > implements EuclideanSpace, IterableRealInterval< T >
+{
+ private final KDTreeData< T > treeData;
- /**
- * maximum of each dimension.
- */
- final protected double[] max;
+ final KDTreeImpl impl;
/**
- * A KDTreeNode that stores its value as a reference.
+ * Access to underlying data for serialization.
*/
- protected final static class ValueNode< T > extends KDTreeNode< T >
+ public KDTreeData< T > treeData()
{
- protected final T value;
-
- /**
- * @param value
- * reference to the node's value
- * @param position
- * coordinates of this node
- * @param dimension
- * dimension along which this node divides the space
- * @param left
- * left child node
- * @param right
- * right child node
- */
- public ValueNode( final T value, final RealLocalizable position, final int dimension, final ValueNode< T > left, final ValueNode< T > right )
- {
- super( position, dimension, left, right );
- this.value = value;
- }
-
- protected ValueNode( final ValueNode< T > node )
- {
- super( node );
- this.value = node.value;
- }
-
- @Override
- public T get()
- {
- return value;
- }
-
- @Override
- public ValueNode< T > copy()
- {
- return new ValueNode< T >( this );
- }
-
- @Override
- public String toString()
- {
- return "node " + getSplitDimension() + " ? " + getSplitCoordinate() + " | " + value;
- }
+ return treeData;
}
/**
- * A KDTreeNode that stores its value as a Sampler.
+ * Access to pure coordinate kD Tree implementation.
*/
- protected static final class SamplerNode< T > extends KDTreeNode< T >
+ public KDTreeImpl impl()
{
- protected final Sampler< T > sampler;
-
- /**
- * @param sampler
- * a sampler providing the node's value
- * @param position
- * coordinates of this node
- * @param dimension
- * dimension along which this node divides the space
- * @param left
- * left child node
- * @param right
- * right child node
- */
- public SamplerNode( final Sampler< T > sampler, final RealLocalizable position, final int dimension, final SamplerNode< T > left, final SamplerNode< T > right )
- {
- super( position, dimension, left, right );
- this.sampler = sampler;
- }
-
- protected SamplerNode( final SamplerNode< T > node )
- {
- super( node );
- this.sampler = node.sampler.copy();
- }
-
- @Override
- public T get()
- {
- return sampler.get();
- }
-
- @Override
- public SamplerNode< T > copy()
- {
- return new SamplerNode< T >( this );
- }
-
- @Override
- public String toString()
- {
- return "node " + getSplitDimension() + " ? " + getSplitCoordinate() + " | " + sampler.get();
- }
+ return impl;
}
/**
@@ -183,58 +41,22 @@ public String toString()
*
*
* @param values
- * a list of values
+ * a list of values
* @param positions
- * a list of positions corresponding to the values
+ * a list of positions corresponding to the values
*/
public < L extends RealLocalizable > KDTree( final List< T > values, final List< L > positions )
{
- assert values.size() == positions.size();
-
- this.n = positions.get( 0 ).numDimensions();
- this.size = positions.size();
-
- // test that dimensionality is preserved
- assert ( verifyDimensions( positions, n ) );
-
- this.min = new double[ n ];
- this.max = new double[ n ];
- for ( int d = 0; d < n; ++d )
- {
- min[ d ] = Double.MAX_VALUE;
- max[ d ] = -Double.MAX_VALUE;
- }
- for ( final L position : positions )
- {
- for ( int d = 0; d < n; ++d )
- {
- final double x = position.getDoublePosition( d );
-
- if ( x < min[ d ] )
- min[ d ] = x;
- if ( x > max[ d ] )
- max[ d ] = x;
- }
- }
+ this( verifySize(values, positions), values, positions );
+ }
- if ( values == positions )
- {
- if ( positions instanceof java.util.RandomAccess )
- root = makeNode( positions, 0, positions.size() - 1, 0 );
- else
- root = makeNode( positions.listIterator(), positions.listIterator( positions.size() ), 0 );
- }
- else
- {
- final int[] permutation = new int[ positions.size() ];
- for ( int k = 0; k < permutation.length; ++k )
- permutation[ k ] = k;
-
- if ( positions instanceof java.util.RandomAccess )
- root = makeNode( positions, 0, positions.size() - 1, 0, values, permutation );
- else
- root = makeNode( positions.listIterator(), positions.listIterator( positions.size() ), 0, values, permutation );
- }
+ private static int verifySize( final List< ? > values, final List< ? > positions )
+ {
+ if ( values.size() != positions.size() )
+ throw new IllegalArgumentException( "The list of values and the list of positions provided to KDTree should have the same size." );
+ if ( positions.isEmpty() )
+ throw new IllegalArgumentException( "List of positions is empty. At least one point is requires to construct a KDTree." );
+ return values.size();
}
/**
@@ -246,466 +68,209 @@ public < L extends RealLocalizable > KDTree( final List< T > values, final List<
*/
public KDTree( final IterableRealInterval< T > interval )
{
- this.n = interval.numDimensions();
- this.size = interval.size();
- this.min = new double[ n ];
- interval.realMin( this.min );
- this.max = new double[ n ];
- interval.realMax( this.max );
- final ArrayList< RealCursor< T > > values = new ArrayList< RealCursor< T > >( ( int ) interval.size() );
- final RealCursor< T > cursor = interval.localizingCursor();
- while ( cursor.hasNext() )
- {
- cursor.next();
- values.add( cursor.copy() );
- }
- root = makeSamplerNode( values, 0, values.size() - 1, 0 );
+ this( verifySize( interval ), interval, positionsIterable( interval ) );
}
- /**
- * Check whether all positions in the positions list have dimension n.
- *
- * @return true, if all positions have dimension n.
- */
- protected static < L extends RealLocalizable > boolean verifyDimensions( final List< L > positions, final int n )
- {
- for ( final L position : positions )
- if ( position.numDimensions() != n )
- return false;
- return true;
- }
+ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
- /**
- * Compare RealLocalizables by comparing their coordinates in dimension d.
- */
- public static final class DimComparator< L extends RealLocalizable > implements Comparator< L >
+ private static int verifySize( final IterableRealInterval< ? > interval )
{
- final int d;
-
- public DimComparator( final int d )
- {
- this.d = d;
- }
-
- @Override
- public int compare( final L o1, final L o2 )
- {
- final float diff = o1.getFloatPosition( d ) - o2.getFloatPosition( d );
- return ( diff < 0 ) ? -1 : ( diff > 0 ? 1 : 0 );
- }
+ final long size = interval.size();
+ if ( size > MAX_ARRAY_SIZE )
+ throw new IllegalArgumentException( "Interval contains too many points to store in KDTree" );
+ else if ( size <= 0 )
+ throw new IllegalArgumentException( "Interval is empty. At least one point is requires to construct a KDTree." );
+ return ( int ) size;
}
- /**
- * Construct the tree by recursively adding nodes. The sublist of positions
- * between indices i and j (inclusive) is split at the median element with
- * respect to coordinates in the given dimension d. The median becomes the
- * new node which is returned. The left and right partitions of the sublist
- * are processed recursively and form the left and right subtrees of the
- * node.
- *
- * @param positions
- * list of positions
- * @param i
- * start index of sublist to process
- * @param j
- * end index of sublist to process
- * @param d
- * dimension along which to split the sublist
- * @param values
- * list of values corresponding to permuted positions
- * @param permutation
- * the index of the values element at index k is permutation[k]
- * @return a new node containing the subtree of the given sublist of
- * positions.
- */
- protected < L extends RealLocalizable > ValueNode< T > makeNode( final List< L > positions, final int i, final int j, final int d, final List< T > values, final int[] permutation )
+ private static < A > Iterable< RealLocalizable > positionsIterable( IterableRealInterval< A > sourceInterval )
{
- if ( j > i )
- {
- final int k = i + ( j - i ) / 2;
- KthElement.kthElement( i, j, k, positions, permutation, new DimComparator< L >( d ) );
-
- final int dChild = ( d + 1 == n ) ? 0 : d + 1;
- return new ValueNode< T >( values.get( permutation[ k ] ), positions.get( k ), d, makeNode( positions, i, k - 1, dChild, values, permutation ), makeNode( positions, k + 1, j, dChild, values, permutation ) );
- }
- else if ( j == i )
- {
- return new ValueNode< T >( values.get( permutation[ i ] ), positions.get( i ), d, null, null );
- }
- else
+ return new AbstractConvertedIterableRealInterval< A, RealLocalizable >( sourceInterval )
{
- return null;
- }
- }
- /**
- * Construct the tree by recursively adding nodes. The sublist of positions
- * between iterators first and last is split at the median element with
- * respect to coordinates in the given dimension d. The median becomes the
- * new node which is returned. The left and right partitions of the sublist
- * are processed recursively and form the left and right subtrees of the
- * node.
- *
- * @param first
- * first element of the sublist of positions
- * @param last
- * last element of the sublist of positions
- * @param d
- * dimension along which to split the sublist
- * @param values
- * list of values corresponding to permuted positions
- * @param permutation
- * the index of the values element at index k is permutation[k]
- * @return a new node containing the subtree of the given sublist of
- * positions.
- */
- protected < L extends RealLocalizable > ValueNode< T > makeNode( final ListIterator< L > first, final ListIterator< L > last, final int d, final List< T > values, final int[] permutation )
- {
- final int i = first.nextIndex();
- final int j = last.previousIndex();
- if ( j > i )
- {
- final int k = i + ( j - i ) / 2;
- KthElement.kthElement( first, last, k, permutation, new DimComparator< L >( d ) );
- first.previous();
- final L current = first.next();
-
- final int dChild = ( d + 1 == n ) ? 0 : d + 1;
-
- // Node< T > right = makeNode( elements, k + 1, j, dChild );
- for ( int c = j - last.previousIndex(); c > 0; --c )
- last.next();
- final ValueNode< T > right = makeNode( first, last, dChild, values, permutation );
-
- // Node< T > left = makeNode( elements, i, k - 1, dChild );
- for ( int c = first.nextIndex() - i; c > 0; --c )
- first.previous();
- for ( int c = last.nextIndex() - k; c > 0; --c )
- last.previous();
- final ValueNode< T > left = makeNode( first, last, dChild, values, permutation );
-
- return new ValueNode< T >( values.get( permutation[ k ] ), current, d, left, right );
- }
- else if ( j == i )
- {
- final L current = first.next();
- return new ValueNode< T >( values.get( permutation[ i ] ), current, d, null, null );
- }
- else
- {
- return null;
- }
- }
+ class Cursor extends AbstractConvertedRealCursor< A, RealLocalizable >
+ {
+ Cursor( final RealCursor< A > source )
+ {
+ super( source );
+ }
+
+ @Override
+ public RealLocalizable get()
+ {
+ return source;
+ }
+
+ @Override
+ public Cursor copy()
+ {
+ return new Cursor( source.copy() );
+ }
+ }
- /**
- * See {@link #makeNode(List, int, int, int, List, int[])}. Here, no values are
- * attached to the nodes (or rather the positions are the values).
- *
- * @param elements
- * list of elements (positions and values at the same time)
- * @param i
- * start index of sublist to process
- * @param j
- * end index of sublist to process
- * @param d
- * dimension along which to split the sublist
- */
- @SuppressWarnings( "unchecked" )
- protected < L extends RealLocalizable > ValueNode< T > makeNode( final List< L > elements, final int i, final int j, final int d )
- {
- if ( j > i )
- {
- final int k = i + ( j - i ) / 2;
- KthElement.kthElement( i, j, k, elements, new DimComparator< L >( d ) );
+ @Override
+ public AbstractConvertedRealCursor< A, RealLocalizable > cursor()
+ {
+ return new Cursor( sourceInterval.cursor() );
+ }
- final int dChild = ( d + 1 == n ) ? 0 : d + 1;
- return new ValueNode< T >( ( T ) elements.get( k ), elements.get( k ), d, makeNode( elements, i, k - 1, dChild ), makeNode( elements, k + 1, j, dChild ) );
- }
- else if ( j == i )
- {
- return new ValueNode< T >( ( T ) elements.get( i ), elements.get( i ), d, null, null );
- }
- else
- {
- return null;
- }
+ @Override
+ public AbstractConvertedRealCursor< A, RealLocalizable > localizingCursor()
+ {
+ return new Cursor( sourceInterval.localizingCursor() );
+ }
+ };
}
- /**
- * See {@link #makeNode(ListIterator, ListIterator, int, List, int[])}. Here, no
- * values are attached to the nodes (or rather the positions are the
- * values).
- *
- * @param first
- * first element of the sublist to process
- * @param last
- * last element of the sublist to process
- * @param d
- * dimension along which to split the sublist
- */
- @SuppressWarnings( "unchecked" )
- protected < L extends RealLocalizable > ValueNode< T > makeNode( final ListIterator< L > first, final ListIterator< L > last, final int d )
+ public < L extends RealLocalizable > KDTree( final int numPoints, final Iterable< T > values, final Iterable< L > positions )
{
- final int i = first.nextIndex();
- final int j = last.previousIndex();
- if ( j > i )
- {
- final int k = i + ( j - i ) / 2;
- KthElement.kthElement( first, last, k, new DimComparator< L >( d ) );
- first.previous();
- final L current = first.next();
-
- final int dChild = ( d + 1 == n ) ? 0 : d + 1;
-
- // Node< T > right = makeNode( elements, k + 1, j, dChild );
- for ( int c = j - last.previousIndex(); c > 0; --c )
- last.next();
- final ValueNode< T > right = makeNode( first, last, dChild );
-
- // Node< T > left = makeNode( elements, i, k - 1, dChild );
- for ( int c = first.nextIndex() - i; c > 0; --c )
- first.previous();
- for ( int c = last.nextIndex() - k; c > 0; --c )
- last.previous();
- final ValueNode< T > left = makeNode( first, last, dChild );
-
- return new ValueNode< T >( ( T ) current, current, d, left, right );
- }
- else if ( j == i )
- {
- final L current = first.next();
- return new ValueNode< T >( ( T ) current, current, d, null, null );
- }
- else
- {
- return null;
- }
+ // TODO make storeValuesAsNativeImg a parameter
+ this( KDTreeData.create( numPoints, values, positions, true ) );
}
- /**
- * Construct the tree by recursively adding nodes. The sublist of elements
- * between indices i and j (inclusive) is split at the median element with
- * respect to coordinates in the given dimension d. The median becomes the
- * new node which is returned. The left and right partitions of the sublist
- * are processed recursively and form the left and right subtrees of the
- * node. (The elements of the list are RealCursors which provide coordinates
- * and values.)
- *
- * @param elements
- * list of elements (positions and values at the same time)
- * @param i
- * start index of sublist to process
- * @param j
- * end index of sublist to process
- * @param d
- * dimension along which to split the sublist
- * @return a new node containing the subtree of the given sublist of
- * elements
- */
- protected SamplerNode< T > makeSamplerNode( final List< RealCursor< T > > elements, final int i, final int j, final int d )
+ // construct with pre-built data, e.g., from deserialization
+ public KDTree( final KDTreeData< T > data )
{
- if ( j > i )
- {
- final int k = i + ( j - i ) / 2;
- KthElement.kthElement( i, j, k, elements, new DimComparator< RealCursor< T > >( d ) );
-
- final int dChild = ( d + 1 == n ) ? 0 : d + 1;
- return new SamplerNode< T >( elements.get( k ), elements.get( k ), d, makeSamplerNode( elements, i, k - 1, dChild ), makeSamplerNode( elements, k + 1, j, dChild ) );
- }
- else if ( j == i )
- {
- return new SamplerNode< T >( elements.get( i ), elements.get( i ), d, null, null );
- }
- else
- {
- return null;
- }
+ treeData = data;
+ impl = ( data.layout() == FLAT )
+ ? KDTreeImpl.create( data.flatPositions(), data.numDimensions() )
+ : KDTreeImpl.create( data.positions() );
}
/**
* Get the root node.
*
* @return the root node.
+ *
+ * @deprecated
+ * {@link KDTreeNode} is now a re-usable proxy (like {@code NativeType}).
+ * To work with existing code, {@link KDTreeNode#left()}, {@link
+ * KDTreeNode#right()}, {@link KDTree#getRoot()} etc create new objects in each
+ * call, instead of re-using existing proxies.
+ * Code using that should be rewritten to reuse proxies, if possible.
*/
+ @Deprecated
public KDTreeNode< T > getRoot()
{
- return root;
+ return new KDTreeNode<>( this ).setNodeIndex( impl.root() );
}
@Override
public int numDimensions()
{
- return n;
- }
-
- public String toString( final KDTreeNode< T > left, final String indent )
- {
- if ( left == null )
- return "";
-
- return indent + "- " + left.toString() + "\n" + toString( left.left, indent + " " ) + toString( left.right, indent + " " );
- }
-
- @Override
- public String toString()
- {
- return toString( root, "" );
+ return impl.numDimensions();
}
@Override
public double realMin( final int d )
{
- return min[ d ];
- }
-
- @Override
- public void realMin( final double[] m )
- {
- for ( int d = 0; d < n; ++d )
- m[ d ] = min[ d ];
- }
-
- @Override
- public void realMin( final RealPositionable m )
- {
- m.setPosition( min );
+ return treeData.boundingBox().realMin( d );
}
@Override
public double realMax( final int d )
{
- return max[ d ];
- }
-
- @Override
- public void realMax( final double[] m )
- {
- for ( int d = 0; d < n; ++d )
- m[ d ] = max[ d ];
- }
-
- @Override
- public void realMax( final RealPositionable m )
- {
- m.setPosition( max );
+ return treeData.boundingBox().realMax( d );
}
@Override
- public long size()
+ public KDTreeCursor cursor()
{
- return size;
+ return new KDTreeCursor();
}
- @Override
- public Object iterationOrder()
+ public final class KDTreeCursor extends KDTreeNode< T > implements RealCursor< T >
{
- return this; // iteration order is only compatible with ourselves
- }
-
- public final class KDTreeCursor implements RealCursor< T >
- {
- private final KDTree< T > tree;
-
- private final ArrayDeque< KDTreeNode< T > > nodes;
-
- private KDTreeNode< T > currentNode;
-
- public KDTreeCursor( final KDTree< T > kdtree )
+ KDTreeCursor()
{
- this.tree = kdtree;
- final int capacity = 2 + ( int ) ( Math.log( kdtree.size() ) / Math.log( 2 ) );
- this.nodes = new ArrayDeque< KDTreeNode< T > >( capacity );
+ super( KDTree.this );
reset();
}
- public KDTreeCursor( final KDTreeCursor c )
- {
- this.tree = c.tree;
- this.nodes = c.nodes.clone();
- this.currentNode = c.currentNode;
- }
-
@Override
- public void localize( final float[] position )
- {
- currentNode.localize( position );
- }
-
- @Override
- public void localize( final double[] position )
- {
- currentNode.localize( position );
- }
-
- @Override
- public float getFloatPosition( final int d )
- {
- return currentNode.getFloatPosition( d );
- }
-
- @Override
- public double getDoublePosition( final int d )
+ public void fwd()
{
- return currentNode.getDoublePosition( d );
+ setNodeIndex( nodeIndex() + 1 );
}
@Override
- public int numDimensions()
+ public void reset()
{
- return n;
+ setNodeIndex( -1 );
}
@Override
- public T get()
+ public boolean hasNext()
{
- return currentNode.get();
+ return nodeIndex() < impl.size() - 1;
}
@Override
public KDTreeCursor copy()
{
- return new KDTreeCursor( this );
+ final KDTreeCursor copy = new KDTreeCursor();
+ copy.setNodeIndex( nodeIndex() );
+ return copy;
}
+ }
- @Override
- public void fwd()
- {
- if ( nodes.isEmpty() )
- currentNode = null;
- else
- {
- currentNode = nodes.pop();
- if ( currentNode.left != null )
- nodes.push( currentNode.left );
- if ( currentNode.right != null )
- nodes.push( currentNode.right );
- }
- }
+ @Override
+ public KDTreeCursor localizingCursor()
+ {
+ return cursor();
+ }
- @Override
- public void reset()
- {
- nodes.clear();
- nodes.push( tree.getRoot() );
- currentNode = null;
- }
+ @Override
+ public KDTreeCursor iterator()
+ {
+ return cursor();
+ }
- @Override
- public boolean hasNext()
- {
- return !nodes.isEmpty();
- }
+ @Override
+ public long size()
+ {
+ return impl.size();
}
@Override
- public KDTreeCursor cursor()
+ public Object iterationOrder()
{
- return new KDTreeCursor( this );
+ return this; // iteration order is only compatible with ourselves
}
@Override
- public KDTreeCursor localizingCursor()
+ public String toString()
+ {
+ return toString( impl.root(), "", createNode() );
+ }
+
+ private String toString( final int node, final String indent, final KDTreeNode< T > ref )
+ {
+ if ( node < 0 )
+ return "";
+ return indent + "- " + ref.setNodeIndex( node ).toString() + "\n"
+ + toString( impl.left( node ), indent + " ", ref )
+ + toString( impl.right( node ), indent + " ", ref );
+ }
+
+ /**
+ * Create a re-usable {@link KDTreeNode} proxy linked to this tree.
+ * {@link KDTreeNode#setNodeIndex(int)} can be used to point the proxy to a
+ * particular node in the tree.
+ */
+ public KDTreeNode< T > createNode() {
+ return new KDTreeNode<>( this );
+ }
+
+ KDTreeNode< T > left( final KDTreeNode< T > parent )
+ {
+ final int c = impl.left( parent.nodeIndex() );
+ return c < 0 ? null : createNode().setNodeIndex( c );
+ }
+
+ KDTreeNode< T > right( final KDTreeNode< T > parent )
{
- return new KDTreeCursor( this );
+ final int c = impl.right( parent.nodeIndex() );
+ return c < 0 ? null : createNode().setNodeIndex( c );
}
}
diff --git a/src/main/java/net/imglib2/KDTreeNode.java b/src/main/java/net/imglib2/KDTreeNode.java
index 4692a804a..6a0d4d421 100644
--- a/src/main/java/net/imglib2/KDTreeNode.java
+++ b/src/main/java/net/imglib2/KDTreeNode.java
@@ -1,107 +1,80 @@
-/*
- * #%L
- * ImgLib2: a general-purpose, multidimensional image processing library.
- * %%
- * Copyright (C) 2009 - 2023 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld,
- * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke,
- * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner,
- * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert,
- * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin,
- * Jean-Yves Tinevez and Michael Zinsmaier.
- * %%
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- * #L%
- */
-
package net.imglib2;
+import java.util.function.IntFunction;
/**
- * Abstract base class for nodes in a KDTree. A KDTreeNode has coordinates and a
- * value. It provides the coordinates via the {@link RealLocalizable} interface.
- * It provides the value via {@link Sampler#get()}.
+ * Proxy for a node in a KDTree. A KDTreeNode has coordinates and a value. It
+ * provides the coordinates via the {@link RealLocalizable} interface. It
+ * provides the value via {@link Sampler#get()}.
*
* @param
* value type.
*
- *
* @author Tobias Pietzsch
*/
-public abstract class KDTreeNode< T > implements RealLocalizable, Sampler< T >
+public class KDTreeNode< T > implements RealLocalizable, Sampler< T >
{
- /**
- * number of dimensions of the space (that is, k).
- */
- protected final int n;
+ private final KDTree< T > tree;
+
+ private int nodeIndex;
+
+ private IntFunction< T > values;
+
+ KDTreeNode( final KDTree< T > tree )
+ {
+ this.tree = tree;
+ }
/**
- * coordinates of the node.
+ * Make this proxy refer to the given {@code nodeIndex} in the associated tree.
+ *
+ * @return {@code this}
*/
- protected final double[] pos;
+ public KDTreeNode< T > setNodeIndex( final int nodeIndex )
+ {
+ this.nodeIndex = nodeIndex;
+ return this;
+ }
/**
- * dimension along which this node divides the space.
+ * Get the {@code nodeIndex} which this proxy currently refers to.
*/
- protected final int splitDimension;
+ public int nodeIndex() {
+ return nodeIndex;
+ }
/**
* Left child of this node. All nodes x in the left subtree have
* {@code x.pos[splitDimension] <= this.pos[splitDimension]}.
+ *
+ * @deprecated
+ * {@link KDTreeNode} is now a re-usable proxy (like {@code NativeType}).
+ * To work with existing code, {@link KDTreeNode#left()}, {@link
+ * KDTreeNode#right()}, {@link KDTree#getRoot()} etc create new objects in each
+ * call, instead of re-using existing proxies.
+ * Code using that should be rewritten to reuse proxies, if possible.
*/
- public final KDTreeNode< T > left;
+ @Deprecated
+ public KDTreeNode< T > left()
+ {
+ return tree.left( this );
+ }
/**
* Right child of this node. All nodes x in the right subtree have
* {@code x.pos[splitDimension] >= this.pos[splitDimension]}.
+ *
+ * @deprecated
+ * {@link KDTreeNode} is now a re-usable proxy (like {@code NativeType}).
+ * To work with existing code, {@link KDTreeNode#left()}, {@link
+ * KDTreeNode#right()}, {@link KDTree#getRoot()} etc create new objects in each
+ * call, instead of re-using existing proxies.
+ * Code using that should be rewritten to reuse proxies, if possible.
*/
- public final KDTreeNode< T > right;
-
- /**
- * @param position
- * coordinates of this node
- * @param dimension
- * dimension along which this node divides the space
- * @param left
- * left child node
- * @param right
- * right child node
- */
- public KDTreeNode( final RealLocalizable position, final int dimension, final KDTreeNode< T > left, final KDTreeNode< T > right )
+ @Deprecated
+ public KDTreeNode< T > right()
{
- this.n = position.numDimensions();
- this.pos = new double[ n ];
- position.localize( pos );
- this.splitDimension = dimension;
- this.left = left;
- this.right = right;
- }
-
- protected KDTreeNode( final KDTreeNode< T > node )
- {
- this.n = node.n;
- this.pos = node.pos.clone();
- this.splitDimension = node.splitDimension;
- this.left = node.left;
- this.right = node.right;
+ return tree.right( this );
}
/**
@@ -111,79 +84,62 @@ protected KDTreeNode( final KDTreeNode< T > node )
*/
public final int getSplitDimension()
{
- return splitDimension;
+ return tree.impl.splitDimension( nodeIndex );
}
/**
- * Get the position along {@link KDTreeNode#getSplitDimension()} where this
+ * Get the position along {@link net.imglib2.KDTreeNode#getSplitDimension()} where this
* node divides the space.
*
* @return splitting position.
*/
public final double getSplitCoordinate()
{
- return pos[ splitDimension ];
+ return getDoublePosition( getSplitDimension() );
}
@Override
- public final int numDimensions()
+ public int numDimensions()
{
- return n;
+ return tree.numDimensions();
}
@Override
- public final void localize( final float[] position )
+ public double getDoublePosition( final int d )
{
- for ( int d = 0; d < n; ++d )
- position[ d ] = ( float ) pos[ d ];
+ return tree.impl.getDoublePosition( nodeIndex, d );
}
@Override
- public final void localize( final double[] position )
+ public T get()
{
- for ( int d = 0; d < n; ++d )
- position[ d ] = pos[ d ];
+ if ( values == null )
+ values = tree.treeData().valuesSupplier().get();
+ return values.apply( nodeIndex );
}
@Override
- public final float getFloatPosition( final int d )
+ public KDTreeNode< T > copy()
{
- return ( float ) pos[ d ];
+ final KDTreeNode< T > copy = new KDTreeNode<>( tree );
+ copy.setNodeIndex( nodeIndex );
+ return copy;
}
- @Override
- public final double getDoublePosition( final int d )
- {
- return pos[ d ];
- }
-
- @Override
- public abstract KDTreeNode< T > copy();
-
/**
* Compute the squared distance from p to this node.
*/
public final float squDistanceTo( final float[] p )
{
- float sum = 0;
- for ( int d = 0; d < n; ++d )
- {
- sum += ( pos[ d ] - p[ d ] ) * ( pos[ d ] - p[ d ] );
- }
- return sum;
+ return tree.impl.squDistance( nodeIndex, p );
}
/**
* Compute the squared distance from p to this node.
*/
- public final double squDistanceTo( final double[] p )
+ public double squDistanceTo( final double[] p )
{
- double sum = 0;
- for ( int d = 0; d < n; ++d )
- {
- sum += ( pos[ d ] - p[ d ] ) * ( pos[ d ] - p[ d ] );
- }
- return sum;
+ return tree.impl.squDistance( nodeIndex, p );
}
/**
@@ -191,11 +147,6 @@ public final double squDistanceTo( final double[] p )
*/
public final double squDistanceTo( final RealLocalizable p )
{
- double sum = 0;
- for ( int d = 0; d < n; ++d )
- {
- sum += ( pos[ d ] - p.getDoublePosition( d ) ) * ( pos[ d ] - p.getDoublePosition( d ) );
- }
- return sum;
+ return tree.impl.squDistance( nodeIndex, p );
}
}
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeData.java b/src/main/java/net/imglib2/kdtree/KDTreeData.java
new file mode 100644
index 000000000..761fea25a
--- /dev/null
+++ b/src/main/java/net/imglib2/kdtree/KDTreeData.java
@@ -0,0 +1,323 @@
+package net.imglib2.kdtree;
+
+import java.util.List;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+import net.imglib2.FinalRealInterval;
+import net.imglib2.RandomAccess;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.RealInterval;
+import net.imglib2.RealLocalizable;
+import net.imglib2.img.Img;
+import net.imglib2.img.list.ListImg;
+import net.imglib2.type.NativeType;
+import net.imglib2.util.Util;
+
+import static net.imglib2.kdtree.KDTreeData.PositionsLayout.FLAT;
+
+/**
+ * Stores the KDTree data, that is, positions and values.
+ *
+ * Positions are stored in either {@code FLAT} or {@code NESTED} {@link
+ * PositionsLayout layout}. With {@code NESTED} layout, positions are stored as
+ * a nested {@code double[][]} array where {@code positions[d][i]} is dimension
+ * {@code d} of the {@code i}-th point. With {@code FLAT} layout, positions are
+ * stored as a flat {@code double[]} array, where {@code positions[d + i*n]} is
+ * dimension {@code d} of the {@code i}-th point, with {@code n} the number of
+ * dimensions.
+ *
+ * Values (of type {@code T}) are stored as either a 1D {@code
+ * RandomAccessibleInterval}, or a {@code List}. Individual values can be
+ * accessed by {@link #valuesSupplier()}{@code .get().apply(i)}. {@code
+ * valueSupplier().get()} returns a reusable {@code IntFunction}. Here {@code
+ * T} maybe a proxy that is reused in subsequent {@code apply(i)}.
+ *
+ * {@link #values()} returns all values as a 1D {@code
+ * RandomAccessibleInterval}. (If data is stored as {@code List}, it is
+ * wrapped into a {@code ListImg}.)
+ *
+ * {@link #positions()} returns positions in nested {@code double[][]} (which is
+ * created if internal storage is {@code FLAT}). {@link #flatPositions()}
+ * returns flat {@code double[]} if internal storage is {@code FLAT}, otherwise
+ * {@code null}.
+ *
+ * @param
+ * the type of values stored in the tree.
+ */
+public class KDTreeData< T >
+{
+ public enum PositionsLayout
+ {
+ FLAT,
+ NESTED
+ }
+
+ private final int numDimensions;
+ private final int numPoints;
+
+ private final PositionsLayout layout;
+ private final double[][] positions;
+ private final double[] flatPositions;
+
+ private final List< T > valuesList;
+ private final RandomAccessibleInterval< T > valuesImg;
+ private final Supplier< IntFunction< T > > valuesSupplier;
+
+ private final T type;
+
+ private volatile RealInterval boundingBox;
+
+ public KDTreeData( double[][] positions, List< T > values )
+ {
+ numPoints = values.size();
+ numDimensions = positions.length;
+
+ layout = PositionsLayout.NESTED;
+ this.positions = positions;
+ flatPositions = null;
+
+ valuesList = values;
+ valuesImg = null;
+ final IntFunction< T > v = values::get;
+ valuesSupplier = () -> v;
+
+ type = KDTreeUtils.getType( values );
+ }
+
+ public KDTreeData( double[][] positions, List< T > values, RealInterval boundingBox )
+ {
+ this( positions, values );
+ this.boundingBox = boundingBox;
+ }
+
+ public KDTreeData( double[][] positions, RandomAccessibleInterval< T > values )
+ {
+ numPoints = ( int ) values.dimension( 0 );
+ numDimensions = positions.length;
+
+ layout = PositionsLayout.NESTED;
+ this.positions = positions;
+ flatPositions = null;
+
+ valuesList = null;
+ valuesImg = values;
+ valuesSupplier = () -> {
+ final RandomAccess ra = valuesImg.randomAccess();
+ return i -> ra.setPositionAndGet(i);
+ };
+
+ type = Util.getTypeFromInterval( values );
+ }
+
+ public KDTreeData( double[][] positions, RandomAccessibleInterval< T > values, RealInterval boundingBox )
+ {
+ this( positions, values );
+ this.boundingBox = boundingBox;
+ }
+
+ public KDTreeData( double[] positions, List< T > values )
+ {
+ numPoints = values.size();
+ numDimensions = positions.length / numPoints;
+
+ layout = FLAT;
+ this.positions = null;
+ flatPositions = positions;
+
+ valuesList = values;
+ valuesImg = null;
+ final IntFunction< T > v = values::get;
+ valuesSupplier = () -> v;
+
+ type = KDTreeUtils.getType( values );
+ }
+
+ public KDTreeData( double[] positions, List< T > values, RealInterval boundingBox )
+ {
+ this( positions, values );
+ this.boundingBox = boundingBox;
+ }
+
+ public KDTreeData( double[] positions, RandomAccessibleInterval< T > values )
+ {
+ numPoints = ( int ) values.dimension( 0 );
+ numDimensions = positions.length / numPoints;
+
+ layout = FLAT;
+ this.positions = null;
+ flatPositions = positions;
+
+ valuesList = null;
+ valuesImg = values;
+ valuesSupplier = () -> {
+ final RandomAccess ra = valuesImg.randomAccess();
+ return i -> ra.setPositionAndGet(i);
+ };
+
+ type = Util.getTypeFromInterval( values );
+ }
+
+ public KDTreeData( double[] positions, RandomAccessibleInterval< T > values, RealInterval boundingBox )
+ {
+ this( positions, values );
+ this.boundingBox = boundingBox;
+ }
+
+ // TODO could also be Class instead? What is more useful?
+ public T type()
+ {
+ return type;
+ }
+
+ /**
+ * Get positions of points in the tree as a nested {@code double[][]} array
+ * where {@code positions[d][i]} is dimension {@code d} of the {@code i}-th
+ * point.
+ *
+ * For serialisation and usage by the tree.
+ *
+ * Internal storage may be flattened into single {@code double[]} array. In
+ * this case, the nested {@code double[][]} array is created here.
+ */
+ public double[][] positions()
+ {
+ return layout == FLAT ? KDTreeUtils.unflatten( flatPositions, numDimensions ) : positions;
+ }
+
+ /**
+ * Get the values as a 1D {@code RandomAccessibleInterval}, for
+ * serialization. (If the underlying storage is a {@code List}, it will
+ * be wrapped as a {@code ListImg}.)
+ */
+ public RandomAccessibleInterval< T > values()
+ {
+ return valuesImg != null
+ ? valuesImg
+ : ListImg.wrap( valuesList, size() );
+ }
+
+ /**
+ * Get a {@code Supplier} that return {@code IntFunction} to provide
+ * values for a given node indices.. If the returned {@code IntFunction}
+ * is stateful ({@code T} maybe a proxy that is reused in subsequent {@code
+ * apply(i)}} every {@link Supplier#get()} creates a new instance of the
+ * {@code IntFunction}.
+ */
+ public Supplier< IntFunction< T > > valuesSupplier()
+ {
+ return valuesSupplier;
+ }
+
+ /**
+ * Get positions of points in the tree as a flat {@code double[]} where
+ * @code positions[d + i*n]} is dimension {@code d} of the {@code i}-th
+ * point, with {@code n} the number of dimensions.
+ *
+ * For serialisation and usage by the tree.
+ *
+ * Internal storage may be a {@code NESTED} {@code double[][]} array. In
+ * this case, {@code flatPositions()} returns {@code null}.
+ */
+ public double[] flatPositions()
+ {
+ return flatPositions;
+ }
+
+ /**
+ * Get the internal layout of positions.
+ *
+ * Positions are stored in either {@code FLAT} or {@code NESTED} {@link
+ * PositionsLayout layout}. With {@code NESTED} layout, positions are stored
+ * as a nested {@code double[][]} array where {@code positions[d][i]} is
+ * dimension {@code d} of the {@code i}-th point. With {@code FLAT} layout,
+ * positions are stored as a flat {@code double[]} array, where {@code
+ * positions[d + i*n]} is dimension {@code d} of the {@code i}-th point,
+ * with {@code n} the number of dimensions.
+ */
+ public PositionsLayout layout()
+ {
+ return layout;
+ }
+
+ /**
+ * @return dimensionality of points in the tree
+ */
+ public int numDimensions()
+ {
+ return numDimensions;
+ }
+
+ /**
+ * @return number of points in the tree
+ */
+ public int size()
+ {
+ return numPoints;
+ }
+
+ public RealInterval boundingBox()
+ {
+ if ( boundingBox == null )
+ boundingBox = createBoundingBox();
+ return boundingBox;
+ }
+
+ private RealInterval createBoundingBox()
+ {
+ final double[] min = new double[ numDimensions ];
+ final double[] max = new double[ numDimensions ];
+ if ( layout == FLAT )
+ KDTreeUtils.computeMinMax( flatPositions, min, max );
+ else
+ KDTreeUtils.computeMinMax( positions, min, max );
+ return FinalRealInterval.wrap( min, max );
+ }
+
+ /**
+ * Create {@link KDTreeData} from the given {@code values} and {@code positions}).
+ * (copies {@code positions} and sorts into a KDTree structure).
+ *
+ * @param numPoints
+ * number of points (number of elements in {@code values} and {@code positions}).
+ * @param values
+ * values associated with points
+ * @param positions
+ * points positions
+ * @param storeValuesAsNativeImg
+ * If {@code true} and {@code T} is a {@code NativeType},
+ * store values into {@code NativeImg}.
+ * Otherwise, store values as a {@code List}.
+ */
+ public static < L extends RealLocalizable, T > KDTreeData< T > create(
+ final int numPoints,
+ final Iterable< T > values,
+ final Iterable< L > positions,
+ final boolean storeValuesAsNativeImg )
+ {
+ if ( numPoints <= 0 )
+ throw new IllegalArgumentException( "At least one point is required to construct a KDTree." );
+ final int numDimensions = KDTreeUtils.getNumDimensions( positions );
+ final double[][] points = KDTreeUtils.initPositions( numDimensions, numPoints, positions );
+ final int[] tree = KDTreeUtils.makeTree( points );
+ final int[] invtree = KDTreeUtils.invert( tree );
+
+ final boolean useFlatLayout = ( long ) numDimensions * numPoints <= KDTreeUtils.MAX_ARRAY_SIZE;
+ if ( storeValuesAsNativeImg && KDTreeUtils.getType( values ) instanceof NativeType )
+ {
+ @SuppressWarnings( "unchecked" )
+ final Img< T > treeValues = ( Img< T > ) KDTreeUtils.orderValuesImg( invtree, ( Iterable ) values );
+ if ( useFlatLayout )
+ return new KDTreeData<>(KDTreeUtils.reorderToFlatLayout( points, tree ), treeValues);
+ else
+ return new KDTreeData<>(KDTreeUtils.reorder( points, tree ), treeValues);
+ }
+ else
+ {
+ final List< T > treeValues = KDTreeUtils.orderValuesList( invtree, values );
+ if ( useFlatLayout )
+ return new KDTreeData<>(KDTreeUtils.reorderToFlatLayout( points, tree ), treeValues);
+ else
+ return new KDTreeData<>(KDTreeUtils.reorder( points, tree ), treeValues);
+ }
+ }
+}
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeImpl.java b/src/main/java/net/imglib2/kdtree/KDTreeImpl.java
new file mode 100644
index 000000000..2f4468e77
--- /dev/null
+++ b/src/main/java/net/imglib2/kdtree/KDTreeImpl.java
@@ -0,0 +1,229 @@
+package net.imglib2.kdtree;
+
+import net.imglib2.RealLocalizable;
+
+import static net.imglib2.kdtree.KDTreeUtils.leftChildIndex;
+import static net.imglib2.kdtree.KDTreeUtils.parentIndex;
+import static net.imglib2.kdtree.KDTreeUtils.rightChildIndex;
+
+/**
+ * Represents the tree structure, and provides access to node coordinates (but
+ * not values).
+ *
+ * The nodes in the tree are arranged in Eytzinger layout (children of i are at
+ * 2i and 2i+1). Additionally, pivot indices are chosen such that "leaf layers"
+ * are filled from the left.
+ *
+ * For example 10 nodes will always be arranged like this:
+ *
+ * 0
+ * / \
+ * 1 2
+ * / \ / \
+ * 3 4 5 6
+ * / \ /
+ * 7 8 9
+ *
+ *
+ * never like this:
+ *
+ * 0
+ * / \
+ * 1 2
+ * / \ / \
+ * 3 4 5 6
+ * / / /
+ * 7 8 9
+ *
+ *
+ * By choosing pivots in this way, the tree structure is fully
+ * determined. For every node index, the child indices can be calculated
+ * without dependent reads. And iff the calculated child index is less
+ * than the number of nodes, the child exists.
+ */
+public abstract class KDTreeImpl
+{
+ final int numDimensions;
+
+ private final int numPoints;
+
+ static class Nested extends KDTreeImpl
+ {
+ private final double[][] positions;
+
+ Nested( final double[][] positions )
+ {
+ super( positions.length, positions[ 0 ].length );
+ this.positions = positions;
+ }
+
+ @Override
+ public double getDoublePosition( final int i, final int d )
+ {
+ return positions[ d ][ i ];
+ }
+ }
+
+ static class Flat extends KDTreeImpl
+ {
+ private final double[] positions;
+
+ Flat( final double[] positions, final int numDimensions )
+ {
+ super( numDimensions, positions.length / numDimensions );
+ this.positions = positions;
+ }
+
+ @Override
+ public double getDoublePosition( final int i, final int d )
+ {
+ return positions[ numDimensions * i + d ];
+ }
+ }
+
+ KDTreeImpl( final int numDimensions, final int numPoints )
+ {
+ this.numDimensions = numDimensions;
+ this.numPoints = numPoints;
+ }
+
+ /**
+ * Get the root node of the tree.
+ *
+ * @return index of the root node
+ */
+ public int root()
+ {
+ return 0;
+ }
+
+ /**
+ * Get the left child of node {@code i}.
+ *
+ * @param i
+ * node index
+ *
+ * @return index of left child or {@code -1} if no left child exists
+ */
+ public int left( final int i )
+ {
+ return ifExists( leftChildIndex( i ) );
+ }
+
+ /**
+ * Get the right child of node {@code i}.
+ *
+ * @param i
+ * node index
+ *
+ * @return index of right child or {@code -1} if no right child exists
+ */
+ public int right( final int i )
+ {
+ return ifExists( rightChildIndex( i ) );
+ }
+
+ /**
+ * Get the parent of node {@code i}.
+ *
+ * @param i
+ * node index
+ *
+ * @return index of parent
+ */
+ public int parent( final int i )
+ {
+ return i == root() ? -1 : parentIndex( i );
+ }
+
+ /**
+ * If a node with index {@code i} exists, returns {@code i}.
+ * Otherwise, returns {@code -1}.
+ */
+ private int ifExists( final int i )
+ {
+ return i < numPoints ? i : -1;
+ }
+
+ /**
+ * Get the dimension along which node {@code i} divides the space.
+ *
+ * @param i
+ * node index
+ *
+ * @return splitting dimension.
+ */
+ public int splitDimension( final int i )
+ {
+ return ( 31 - Integer.numberOfLeadingZeros( i + 1 ) ) % numDimensions;
+ }
+
+ public abstract double getDoublePosition( final int i, final int d );
+
+ /**
+ * Compute the squared distance from node {@code i} to {@code pos}.
+ */
+ public float squDistance( final int i, final float[] pos )
+ {
+ float sum = 0;
+ for ( int d = 0; d < numDimensions; ++d )
+ {
+ final float diff = pos[ d ] - ( float ) getDoublePosition( i, d );
+ sum += diff * diff;
+ }
+ return sum;
+ }
+
+ /**
+ * Compute the squared distance from node {@code i} to {@code pos}.
+ */
+ public double squDistance( final int i, final double[] pos )
+ {
+ double sum = 0;
+ for ( int d = 0; d < numDimensions; ++d )
+ {
+ final double diff = pos[ d ] - getDoublePosition( i, d );
+ sum += diff * diff;
+ }
+ return sum;
+ }
+
+ /**
+ * Compute the squared distance from node {@code i} to {@code pos}.
+ */
+ public double squDistance( final int i, final RealLocalizable pos )
+ {
+ double sum = 0;
+ for ( int d = 0; d < numDimensions; ++d )
+ {
+ final double diff = pos.getDoublePosition( d ) - getDoublePosition( i, d );
+ sum += diff * diff;
+ }
+ return sum;
+ }
+
+ public int numDimensions()
+ {
+ return numDimensions;
+ }
+
+ public int size()
+ {
+ return numPoints;
+ }
+
+ public int depth()
+ {
+ return 32 - Integer.numberOfLeadingZeros( numPoints );
+ }
+
+ public static KDTreeImpl create( final double[][] positions )
+ {
+ return new Nested( positions );
+ }
+
+ public static KDTreeImpl create( final double[] positions, final int numDimensions )
+ {
+ return new Flat( positions, numDimensions );
+ }
+}
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeUtils.java b/src/main/java/net/imglib2/kdtree/KDTreeUtils.java
new file mode 100644
index 000000000..c972ff12b
--- /dev/null
+++ b/src/main/java/net/imglib2/kdtree/KDTreeUtils.java
@@ -0,0 +1,573 @@
+package net.imglib2.kdtree;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import net.imglib2.RandomAccess;
+import net.imglib2.RealLocalizable;
+import net.imglib2.img.Img;
+import net.imglib2.img.array.ArrayImgFactory;
+import net.imglib2.type.NativeType;
+
+final class KDTreeUtils
+{
+ static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
+
+ /**
+ * If the tree is flattened into an array the left child of node at
+ * index {@code i} has index {@code 2 * i + 1}.
+ */
+ static int leftChildIndex( final int i )
+ {
+ return 2 * i + 1;
+ }
+
+ /**
+ * If the tree is flattened into an array the right child of node at
+ * index {@code i} has index {@code 2 * i + 2}.
+ */
+ static int rightChildIndex( final int i )
+ {
+ return 2 * i + 2;
+ }
+
+ /**
+ * If the tree is flattened into an array the parent of node at
+ * index {@code i} has index {@code (i - 1) / 2} (except for the
+ * root node {@code i==0}).
+ */
+ static int parentIndex( final int i )
+ {
+ return ( i - 1 ) / 2;
+ }
+
+ /**
+ * Copy the coordinates of the given {@code points} to a new {@code double[][] positions} array.
+ * The coordinate in dimension {@code d} of the {@code i}th point is stored at {@code positions[d][i]}.
+ * That is, {@code positions[0]} has all X coordinates, {@code positions[0]} has all Y coordinates, and so on.
+ */
+ static double[][] initPositions(
+ final int numDimensions,
+ final int numPoints,
+ final Iterable< ? extends RealLocalizable > points )
+ {
+ final double[][] positions = new double[ numDimensions ][ numPoints ];
+ final Iterator< ? extends RealLocalizable > ipos = points.iterator();
+ for ( int i = 0; i < numPoints; ++i )
+ {
+ if ( !ipos.hasNext() )
+ throw new IllegalArgumentException( "positions Iterable is empty" );
+ final RealLocalizable pos = ipos.next();
+ for ( int d = 0; d < numDimensions; d++ )
+ positions[ d ][ i ] = pos.getDoublePosition( d );
+ }
+ return positions;
+ }
+
+ /**
+ * Sort the given points into a k-d tree.
+ *
+ * The tree is given as a flat (heap-like) array of point indices, {@code int[] tree}.
+ * The index of the point chosen as the root node is {@code tree[0]}.
+ * The coordinates of the root node are at {@code positions[d][tree[0]]}.
+ *
+ * The indices of the children (less-or-equal and greater-or-equal, respectively) of root are at {@code tree[1]} and {@code tree[2]}, and so on.
+ *
+ * @param positions
+ * The coordinates for the {@code i}th point are stored at {@code positions[d][i]} where {@code d} is the dimension.
+ * See {@link #initPositions(int, int, Iterable)}.
+ *
+ * @return flattened tree of point indices
+ */
+ static int[] makeTree( double[][] positions )
+ {
+ return new MakeTree( positions ).tree;
+ }
+
+ /**
+ * Re-order the node {@code positions} to form a tree corresponding to the index array {@code tree'={0,1,2,...}}.
+ *
+ * @param positions
+ * @param tree
+ *
+ * @return
+ */
+ static double[][] reorder( double[][] positions, int[] tree )
+ {
+ final int numDimensions = positions.length;
+ final int numPoints = positions[ 0 ].length;
+ assert tree.length == numPoints;
+ final double[][] reordered = new double[ numDimensions ][];
+ Arrays.setAll( reordered, d -> reorder( positions[ d ], tree ) );
+ return reordered;
+ }
+
+ /**
+ * Create a new {@code double[]} array that contains the elements of {@code
+ * values}, ordered such that {@code values[order[i]]} is at index {@code i}.
+ */
+ static double[] reorder( final double[] values, final int[] order )
+ {
+ final int size = order.length;
+ final double[] reordered = new double[ size ];
+ Arrays.setAll( reordered, i -> values[ order[ i ] ] );
+ return reordered;
+ }
+
+ /**
+ * Create a new {@code int[]} array that contains the elements of {@code
+ * values}, ordered such that {@code values[order[i]]} is at index {@code i}.
+ */
+ static int[] reorder( final int[] values, final int[] order )
+ {
+ final int size = order.length;
+ final int[] reordered = new int[ size ];
+ Arrays.setAll( reordered, i -> values[ order[ i ] ] );
+ return reordered;
+ }
+
+ /**
+ * Re-order the node {@code positions} to form a tree corresponding to the index array {@code tree={0,1,2,...}}.
+ * Then flatten the result into a 1-D array, interleaving coordinates in all dimensions.
+ *
+ * @param positions
+ * @param tree
+ *
+ * @return
+ */
+ static double[] reorderToFlatLayout( final double[][] positions, final int[] tree )
+ {
+ final int numDimensions = positions.length;
+ final int numPoints = positions[ 0 ].length;
+ assert tree.length == numPoints;
+ if ( ( long ) numDimensions * numPoints > MAX_ARRAY_SIZE )
+ throw new IllegalArgumentException( "positions[][] is too large to be stored in a flat array" );
+ final double[] reordered = new double[ numDimensions * numPoints ];
+ for ( int i = 0; i < numPoints; ++i )
+ for ( int d = 0; d < numDimensions; ++d )
+ reordered[ numDimensions * i + d ] = positions[ d ][ tree[ i ] ];
+ return reordered;
+ }
+
+ /**
+ * Flatten the nested {@code positions} array.
+ *
+ * @param positions positions in nested layout
+ * @return positions in flattened layout
+ */
+ static double[] flatten( double[][] positions )
+ {
+ final int numDimensions = positions.length;
+ final int numPoints = positions[ 0 ].length;
+ final double[] flattened = new double[ numDimensions * numPoints ];
+ for ( int i = 0; i < numPoints; ++i )
+ for ( int d = 0; d < numDimensions; ++d )
+ flattened[ numDimensions * i + d ] = positions[ d ][ i ];
+ return flattened;
+
+ }
+
+ /**
+ * Transform flat {@code positions} array into a nested {@code
+ * double[numDimensions][numPoints]} array.
+ *
+ * With flat layout, positions are stored as a flat {@code double[]} array,
+ * where {@code positions[d + i*n]} is dimension {@code d} of the {@code
+ * i}-th point, with {@code n} the number of dimensions.
+ *
+ * With nested layout, positions are stored as a nested {@code double[][]}
+ * array where {@code positions[d][i]} is dimension {@code d} of the {@code
+ * i}-th point.
+ *
+ * @param positions
+ * positions in flattened layout
+ * @param n
+ * number of dimensions
+ *
+ * @return positions in nested layout
+ */
+ static double[][] unflatten( double[] positions, final int n )
+ {
+ final int numPoints = positions.length / n;
+ final double[][] unflattened = new double[ n ][ numPoints ];
+ for (int i = 0; i < positions.length; ++i )
+ {
+ final int d = i % n;
+ unflattened[ d ][ i / n + d ] = positions[ i ];
+ }
+ return unflattened;
+
+ }
+
+ /**
+ * Compute bounding box of positions in nested layout
+ */
+ static void computeMinMax(final double[][] positions, final double[] min, final double[] max) {
+ final int n = min.length;
+ for (int d = 0; d < n; d++) {
+ double maxd = Double.NEGATIVE_INFINITY;
+ double mind = Double.POSITIVE_INFINITY;
+ for (double v : positions[d]) {
+ if (v < mind) {
+ mind = v;
+ }
+ if (v > maxd) {
+ maxd = v;
+ }
+ }
+ min[d] = mind;
+ max[d] = maxd;
+ }
+ }
+
+ /**
+ * Compute bounding box of positions in flat layout
+ */
+ static void computeMinMax(final double[] flatPositions, final double[] min, final double[] max) {
+ final int n = min.length;
+ Arrays.fill(max, Double.NEGATIVE_INFINITY);
+ Arrays.fill(min, Double.POSITIVE_INFINITY);
+ int d = 0;
+ for (double v : flatPositions) {
+ if (v < min[d]) {
+ min[d] = v;
+ }
+ if (v > max[d]) {
+ max[d] = v;
+ }
+ d = (d + 1) % n;
+ }
+ }
+
+ /**
+ * Invert the given permutation {@code tree}.
+ *
+ * For example, {@code tree = {3, 4, 1, 0, 5, 2}} indicates that coordinates
+ * and value for the node at heap index {@code i} can be found at index
+ * {@code tree[i]} in the respective input list.
+ *
+ * The inverse, {@code inv = {3, 4, 1, 0, 5, 2}} indicates that coordinates
+ * and value at index {@code i} in the respective input list belong to the
+ * node at heap index {@code inv[i]}.
+ *
+ * @param tree a permutation
+ * @return the inverse permutation
+ */
+ static int[] invert( int[] tree )
+ {
+ // For example:
+ // i = 0 1 2 3 4 5
+ // tree = {3, 4, 1, 0, 5, 2}
+ // output = {3, 2, 5, 0, 1, 4}
+
+ final int[] inv = new int[ tree.length ];
+ for ( int i = 0; i < tree.length; i++ )
+ inv[tree[i]] = i;
+ return inv;
+ }
+
+ /**
+ * Re-order the node {@code values} to form a tree corresponding to the index array {@code tree'={0,1,2,...}}.
+ * The tree is given as an {@link #invert(int[]) inverted permutation}, so that we can iterate through the {@code values} in order, putting each at the right index in the returned {@code List}.
+ *
+ * @param invtree
+ * @param values
+ * @param
+ *
+ * @return
+ */
+ static < T > List< T > orderValuesList(
+ final int[] invtree,
+ final Iterable< T > values )
+ {
+ final int size = invtree.length;
+ @SuppressWarnings( "unchecked" )
+ final T[] orderedValues = ( T[] ) new Object[ size ];
+ final Iterator< T > ival = values.iterator();
+ for ( final int i : invtree )
+ {
+ if ( !ival.hasNext() )
+ throw new IllegalArgumentException( "provided values Iterable has fewer elements than required" );
+ orderedValues[ i ] = ival.next();
+ }
+ return Arrays.asList( orderedValues );
+ }
+
+ /**
+ * Re-order the node {@code values} to form a tree corresponding to the index array {@code tree'={0,1,2,...}}.
+ * The tree is given as an {@link #invert(int[]) inverted permutation}, so that we can iterate through the {@code values} in order, putting each at the right index in the returned 1D {@code Img}.
+ *
+ * @param invtree
+ * @param values
+ * @param
+ *
+ * @return
+ */
+ static < T extends NativeType< T > > Img< T > orderValuesImg(
+ final int[] invtree,
+ final Iterable< T > values )
+ {
+ final int size = invtree.length;
+ final Img< T > img = new ArrayImgFactory<>( getType( values ) ).create( size );
+ final RandomAccess< T > orderedValues = img.randomAccess();
+ final Iterator< T > ival = values.iterator();
+ for ( final int i : invtree )
+ {
+ if ( !ival.hasNext() )
+ throw new IllegalArgumentException( "provided values Iterable has fewer elements than required" );
+ orderedValues.setPositionAndGet( i ).set( ival.next() );
+ }
+ return img;
+ }
+
+ /**
+ * Returns the first element of {@code values}.
+ *
+ * @throws IllegalArgumentException
+ * if {@code values} has no elements.
+ */
+ static < T > T getType( Iterable< T > values )
+ {
+ final Iterator< T > ival = values.iterator();
+ if ( !ival.hasNext() )
+ throw new IllegalArgumentException( "values Iterable is empty" );
+ return ival.next();
+ }
+
+ /**
+ * Returns the number of dimensions of the first element of {@code positions}.
+ * If {@code positions} has no elements, returns {@code 0}.
+ *
+ * @param positions
+ * list of points
+ *
+ * @return number of dimensions of the first point
+ */
+ static int getNumDimensions( Iterable< ? extends RealLocalizable > positions )
+ {
+ final Iterator< ? extends RealLocalizable > ipos = positions.iterator();
+ return ipos.hasNext() ? ipos.next().numDimensions() : 0;
+ }
+
+ /**
+ * Swap {@code order[i]} and {@code order[j]}.
+ */
+ private static void swap( final int i, final int j, final int[] order )
+ {
+ final int tmp = order[ i ];
+ order[ i ] = order[ j ];
+ order[ j ] = tmp;
+ }
+
+ /**
+ * Partition a sublist. The list is given by an immutable array of {@code
+ * values}, and an index array that represents the {@code order} of values
+ * in the list. This method only rearranges the {@code order} array.
+ *
+ * A pivot element is chosen by median-of-three method. Then {@code
+ * [i,j]} is reordered, such that all elements before the pivot are
+ * smaller-equal and all elements after the pivot are larger-equal the
+ * pivot. The index of the pivot element is returned.
+ *
+ * @param i
+ * index of first element of the sublist
+ * @param j
+ * index of last element of the sublist
+ * @param values
+ * the array of values of list elements.
+ * @param order
+ * order of list elements. E.g., {@code order[0]=3} means that {@code values[3]} is the first element of the list.
+ * @return index of pivot element
+ */
+ static int partition( int i, int j, final double[] values, final int[] order )
+ {
+ final int len = j - i + 1;
+ if ( len <= 2 )
+ {
+ if ( len <= 0 )
+ throw new IllegalArgumentException();
+ if ( values[ order[ i ] ] > values[ order[ j ] ] )
+ swap( i, j, order );
+ return i;
+ }
+ else
+ {
+ final int m = ( i + j ) / 2;
+ if ( values[ order[ i ] ] > values[ order[ m ] ] )
+ swap( i, m, order );
+ if ( values[ order[ i ] ] > values[ order[ j ] ] )
+ swap( i, j, order );
+ if ( values[ order[ m ] ] > values[ order[ j ] ] )
+ swap( m, j, order );
+ swap( m, i + 1, order );
+ final int p = ++i;
+ final double pivot = values[ order[ p ] ];
+ while ( true )
+ {
+ while ( values[ order[ ++i ] ] < pivot )
+ ;
+ while ( values[ order[ --j ] ] > pivot )
+ ;
+ if ( j < i )
+ break;
+ swap( i, j, order );
+ }
+ swap( p, j, order );
+ return j;
+ }
+ }
+
+ /**
+ * Sort a sublist. The list is given by an immutable array of {@code
+ * values}, and an index array that represents the {@code order} of values
+ * in the list. This method only rearranges the {@code order} array.
+ *
+ * @param i
+ * index of first element of the sublist
+ * @param j
+ * index of last element of the sublist
+ * @param values
+ * the array of values of list elements.
+ * @param order
+ * order of list elements. E.g., {@code order[0]=3} means that {@code values[3]} is the first element of the list.
+ */
+ static void quicksort( final int i, final int j, final double[] values, final int[] order )
+ {
+ if ( 0 <= i && i < j )
+ {
+ final int p = partition( i, j, values, order );
+ quicksort( i, p - 1, values, order );
+ quicksort( p + 1, j, values, order );
+ }
+ }
+
+ private static final class MakeTree
+ {
+ private final int numDimensions;
+
+ private final int numPoints;
+
+ /**
+ * The coordinates for the {@code i}th point are stored at {@code positions[d][k]} where {@code d} is the dimension.
+ */
+ private final double[][] positions;
+
+ /**
+ * Temporary array to keep track of elements.
+ * Initialized to {@code 0, 1, ... } and then permuted when sorting the elements into a tree.
+ */
+ private final int[] indices;
+
+ /**
+ * Node indices in a flattened (heap-like) array.
+ * For example: the index of the root node is {@code tree[0]}.
+ * The coordinates of the root node are at {@code positions[d][tree[0]]} where {@code d} is the dimension.
+ * The children of the root node are at {@code tree[1]} and {@code tree[2]}, and so on.
+ */
+ private final int[] tree;
+
+ private MakeTree( final double[][] positions )
+ {
+ this.positions = positions;
+ numDimensions = positions.length;
+ numPoints = positions[ 0 ].length;
+ indices = new int[ numPoints ];
+ tree = new int[ numPoints ];
+ Arrays.setAll( indices, j -> j );
+ makeNode( 0, numPoints - 1, 0, 0 );
+ }
+
+ /**
+ * Calculate pivot index such that the tree will be arranged in a way that
+ * "leaf layers" are filled from the left.
+ * For example 10 nodes will always be arranged like this:
+ *
+ * 0
+ * / \
+ * 1 2
+ * / \ / \
+ * 3 4 5 6
+ * / \ /
+ * 7 8 9
+ *
+ *
+ * never like this:
+ *
+ * 0
+ * / \
+ * 1 2
+ * / \ / \
+ * 3 4 5 6
+ * / / /
+ * 7 8 9
+ *
+ *
+ * By choosing pivots in this way, the tree structure is fully
+ * determined. For every node index, the child indices can be calculated
+ * without dependent reads. And iff the calculated child index is less
+ * than the number of nodes, the child exists.
+ */
+ private static int pivot( final int len )
+ {
+ final int h = Integer.highestOneBit( len );
+ final int h2 = h >> 1;
+ return ( len - h >= h2 )
+ ? h - 1
+ : len - h2;
+ }
+
+ private void makeNode( final int i, final int j, final int d, final int nodeIndex )
+ {
+ if ( j > i )
+ {
+ final int k = i + pivot( j - i + 1 );
+ kthElement( i, j, k, d );
+ tree[ nodeIndex ] = indices[ k ];
+ final int dChild = ( d + 1 ) % numDimensions;
+ makeNode( i, k - 1, dChild, leftChildIndex( nodeIndex ) );
+ makeNode( k + 1, j, dChild, rightChildIndex( nodeIndex ) );
+ }
+ else if ( j == i )
+ {
+ tree[ nodeIndex ] = indices[ i ];
+ }
+ }
+
+ /**
+ * Partition a sublist of Nodes by their coordinate in the specified
+ * dimension, such that the k-th smallest value is at position {@code
+ * k}, elements before the k-th are smaller or equal and elements after
+ * the k-th are larger or equal.
+ *
+ * @param i
+ * index of first element of the sublist
+ * @param j
+ * index of last element of the sublist
+ * @param k
+ * index for k-th smallest value. {@code i <= k <= j}.
+ * @param compare_d
+ * dimension by which to order the sublist
+ */
+ private void kthElement( int i, int j, final int k, final int compare_d )
+ {
+ while ( true )
+ {
+ final int pivotpos = partition( i, j, positions[ compare_d ], indices );
+ if ( pivotpos > k )
+ {
+ // partition lower half
+ j = pivotpos - 1;
+ }
+ else if ( pivotpos < k )
+ {
+ // partition upper half
+ i = pivotpos + 1;
+ }
+ else
+ return;
+ }
+ }
+ }
+
+ private KDTreeUtils() {}
+}
diff --git a/src/main/java/net/imglib2/kdtree/KNearestNeighborSearchImpl.java b/src/main/java/net/imglib2/kdtree/KNearestNeighborSearchImpl.java
new file mode 100644
index 000000000..fccec79bb
--- /dev/null
+++ b/src/main/java/net/imglib2/kdtree/KNearestNeighborSearchImpl.java
@@ -0,0 +1,125 @@
+package net.imglib2.kdtree;
+
+import java.util.Arrays;
+import net.imglib2.RealLocalizable;
+
+/**
+ * k-nearest-neighbor search on {@link KDTreeImpl}.
+ * Results are node indices.
+ */
+public class KNearestNeighborSearchImpl
+{
+ private final KDTreeImpl tree;
+ private final int numDimensions;
+ private final int numPoints;
+ private final double[] pos;
+ private final int k;
+ private final double[] bestSquDistance;
+ private final int[] bestIndex;
+ private final double[] axisDiffs;
+ private final int[] awayChilds;
+
+ public KNearestNeighborSearchImpl( final KDTreeImpl tree, final int k )
+ {
+ this.tree = tree;
+ numDimensions = tree.numDimensions();
+ numPoints = tree.size();
+ this.k = k;
+ pos = new double[ numDimensions ];
+ bestSquDistance = new double[ k ];
+ bestIndex = new int[ k ];
+ final int depth = tree.depth();
+ axisDiffs = new double[ depth + 1 ];
+ awayChilds = new int[ depth + 1 ];
+ }
+
+ /**
+ * Insert index into list of best nodes.
+ * Also checks whether index will be inserted at all, that is,
+ * whether squDistance < bestSquDistance[k-1]
+ */
+ private void insert( final double squDistance, final int index )
+ {
+ // first check whether index will be inserted at all
+ if ( squDistance < bestSquDistance[ k - 1 ] )
+ {
+
+ // find insertion point, shifting existing elements to make room
+ int i;
+ for ( i = k - 1; i > 0 && squDistance < bestSquDistance[ i - 1 ]; --i )
+ {
+ bestSquDistance[ i ] = bestSquDistance[ i - 1 ];
+ bestIndex[ i ] = bestIndex[ i - 1 ];
+ }
+
+ // insert index at i,
+ bestSquDistance[ i ] = squDistance;
+ bestIndex[ i ] = index;
+ }
+ }
+
+ public void search( final RealLocalizable p )
+ {
+ p.localize( pos );
+ int current = tree.root();
+ int depth = 0;
+ Arrays.fill( bestSquDistance, Double.POSITIVE_INFINITY );
+ Arrays.fill( bestIndex, -1 );
+ while ( true )
+ {
+ insert( tree.squDistance( current, pos ), current );
+
+ final int d = depth % numDimensions;
+ final double axisDiff = pos[ d ] - tree.getDoublePosition( current, d );
+ final boolean leftIsNearBranch = axisDiff < 0;
+
+ // search the near branch
+ final int nearChild = ( 2 * current ) + ( leftIsNearBranch ? 1 : 2 );
+ final int awayChild = ( 2 * current ) + ( leftIsNearBranch ? 2 : 1 );
+ ++depth;
+ awayChilds[ depth ] = awayChild;
+ axisDiffs[ depth ] = axisDiff * axisDiff;
+ if ( nearChild >= numPoints )
+ {
+ while ( awayChilds[ depth ] >= numPoints || axisDiffs[ depth ] > bestSquDistance[ k - 1 ] )
+ {
+ if ( --depth == 0 )
+ {
+ return;
+ }
+ }
+ current = awayChilds[ depth ];
+ awayChilds[ depth ] = numPoints;
+ }
+ else
+ {
+ current = nearChild;
+ }
+ }
+ }
+
+ public int k()
+ {
+ return k;
+ }
+
+ public int bestIndex( final int i )
+ {
+ return bestIndex[ i ];
+ }
+
+ public double bestSquDistance( final int i )
+ {
+ return bestSquDistance[ i ];
+ }
+
+ public KNearestNeighborSearchImpl copy()
+ {
+ final KNearestNeighborSearchImpl copy = new KNearestNeighborSearchImpl( tree, k );
+ System.arraycopy( pos, 0, copy.pos, 0, pos.length );
+ System.arraycopy( bestIndex, 0, copy.bestIndex, 0, bestIndex.length );
+ System.arraycopy( bestSquDistance, 0, copy.bestSquDistance, 0, bestSquDistance.length );
+ return copy;
+ }
+
+}
diff --git a/src/main/java/net/imglib2/kdtree/NearestNeighborSearchImpl.java b/src/main/java/net/imglib2/kdtree/NearestNeighborSearchImpl.java
new file mode 100644
index 000000000..d887bea94
--- /dev/null
+++ b/src/main/java/net/imglib2/kdtree/NearestNeighborSearchImpl.java
@@ -0,0 +1,95 @@
+package net.imglib2.kdtree;
+
+import net.imglib2.RealLocalizable;
+
+/**
+ * Nearest-neighbor search on {@link KDTreeImpl}.
+ * Results are node indices.
+ */
+public class NearestNeighborSearchImpl
+{
+ private final KDTreeImpl tree;
+ private final int numDimensions;
+ private final int numPoints;
+ private final double[] pos;
+ private int bestIndex;
+ private double bestSquDistance;
+ private final double[] axisDiffs;
+ private final int[] awayChilds;
+
+ public NearestNeighborSearchImpl( final KDTreeImpl tree )
+ {
+ this.tree = tree;
+ numDimensions = tree.numDimensions();
+ numPoints = tree.size();
+ pos = new double[ numDimensions ];
+ bestIndex = -1;
+ bestSquDistance = Double.POSITIVE_INFINITY;
+ final int depth = tree.depth();
+ axisDiffs = new double[ depth + 1 ];
+ awayChilds = new int[ depth + 1 ];
+ }
+
+ public void search( final RealLocalizable p )
+ {
+ p.localize( pos );
+ int current = tree.root();
+ int depth = 0;
+ bestSquDistance = ( bestIndex >= 0 ) ? tree.squDistance( bestIndex, pos ) : Double.POSITIVE_INFINITY;
+ while ( true )
+ {
+ final double squDistance = tree.squDistance( current, pos );
+ if ( squDistance < bestSquDistance )
+ {
+ bestSquDistance = squDistance;
+ bestIndex = current;
+ }
+
+ final int d = depth % numDimensions;
+ final double axisDiff = pos[ d ] - tree.getDoublePosition( current, d );
+ final boolean leftIsNearBranch = axisDiff < 0;
+
+ // search the near branch
+ final int nearChild = ( 2 * current ) + ( leftIsNearBranch ? 1 : 2 );
+ final int awayChild = ( 2 * current ) + ( leftIsNearBranch ? 2 : 1 );
+ ++depth;
+ awayChilds[ depth ] = awayChild;
+ axisDiffs[ depth ] = axisDiff * axisDiff;
+ if ( nearChild >= numPoints )
+ {
+ while ( awayChilds[ depth ] >= numPoints || axisDiffs[ depth ] > bestSquDistance )
+ {
+ if ( --depth == 0 )
+ {
+ return;
+ }
+ }
+ current = awayChilds[ depth ];
+ awayChilds[ depth ] = numPoints;
+ }
+ else
+ {
+ current = nearChild;
+ }
+ }
+ }
+
+ public int bestIndex()
+ {
+ return bestIndex;
+ }
+
+ public double bestSquDistance()
+ {
+ return bestSquDistance;
+ }
+
+ public NearestNeighborSearchImpl copy()
+ {
+ final NearestNeighborSearchImpl copy = new NearestNeighborSearchImpl( tree );
+ System.arraycopy( pos, 0, copy.pos, 0, pos.length );
+ copy.bestIndex = bestIndex;
+ copy.bestSquDistance = bestSquDistance;
+ return copy;
+ }
+}
diff --git a/src/main/java/net/imglib2/kdtree/RadiusNeighborSearchImpl.java b/src/main/java/net/imglib2/kdtree/RadiusNeighborSearchImpl.java
new file mode 100644
index 000000000..8d45f2264
--- /dev/null
+++ b/src/main/java/net/imglib2/kdtree/RadiusNeighborSearchImpl.java
@@ -0,0 +1,155 @@
+package net.imglib2.kdtree;
+
+import java.util.Arrays;
+import net.imglib2.RealLocalizable;
+
+import static net.imglib2.kdtree.KDTreeUtils.quicksort;
+import static net.imglib2.kdtree.KDTreeUtils.reorder;
+
+/**
+ * Radius neighbor search on {@link KDTreeImpl}.
+ * Results are node indices.
+ */
+public class RadiusNeighborSearchImpl
+{
+ private final KDTreeImpl tree;
+ private final int numDimensions;
+ private final int numPoints;
+ private final double[] pos;
+ private final double[] axisDiffs;
+ private final int[] awayChilds;
+ private final Neighbors neighbors;
+
+ public RadiusNeighborSearchImpl( final KDTreeImpl tree )
+ {
+ this.tree = tree;
+ numDimensions = tree.numDimensions();
+ numPoints = tree.size();
+ pos = new double[ numDimensions ];
+ final int depth = tree.depth();
+ axisDiffs = new double[ depth + 1 ];
+ awayChilds = new int[ depth + 1 ];
+ neighbors = new Neighbors();
+ }
+
+ public void search( final RealLocalizable p, final double radius, final boolean sortResults )
+ {
+ assert radius >= 0;
+ final double squRadius = radius * radius;
+ p.localize( pos );
+ neighbors.clear();
+ int current = tree.root();
+ int depth = 0;
+ while ( true )
+ {
+ final double squDistance = tree.squDistance( current, pos );
+ if ( squDistance < squRadius )
+ {
+ neighbors.add( squDistance, current );
+ }
+
+ final int d = depth % numDimensions;
+ final double axisDiff = pos[ d ] - tree.getDoublePosition( current, d );
+ final boolean leftIsNearBranch = axisDiff < 0;
+
+ // search the near branch
+ final int nearChild = ( 2 * current ) + ( leftIsNearBranch ? 1 : 2 );
+ final int awayChild = ( 2 * current ) + ( leftIsNearBranch ? 2 : 1 );
+ ++depth;
+ awayChilds[ depth ] = awayChild;
+ axisDiffs[ depth ] = axisDiff * axisDiff;
+ if ( nearChild >= numPoints )
+ {
+ while ( awayChilds[ depth ] >= numPoints || axisDiffs[ depth ] > squRadius )
+ {
+ if ( --depth == 0 )
+ {
+ if ( sortResults )
+ {
+ neighbors.sort();
+ }
+ return;
+ }
+ }
+ current = awayChilds[ depth ];
+ awayChilds[ depth ] = numPoints;
+ }
+ else
+ {
+ current = nearChild;
+ }
+ }
+ }
+
+ public int numNeighbors()
+ {
+ return neighbors.size;
+ }
+
+ public int bestIndex( final int i )
+ {
+ return neighbors.indices[ i ];
+ }
+
+ public double bestSquDistance( final int i )
+ {
+ return neighbors.distances[ i ];
+ }
+
+ public RadiusNeighborSearchImpl copy()
+ {
+ final RadiusNeighborSearchImpl copy = new RadiusNeighborSearchImpl( tree );
+ System.arraycopy( pos, 0, copy.pos, 0, pos.length );
+ copy.neighbors.makeCopyOf( neighbors );
+ return copy;
+ }
+
+ static class Neighbors
+ {
+ double[] distances;
+ int[] indices;
+ int size;
+
+ Neighbors()
+ {
+ final int capacity = 10;
+ distances = new double[ capacity ];
+ indices = new int[ capacity ];
+ }
+
+ void clear()
+ {
+ size = 0;
+ }
+
+ void add( final double distance, final int index )
+ {
+ if ( distances.length <= size )
+ {
+ // reallocate
+ final int newLength = distances.length * 2;
+ distances = Arrays.copyOf( distances, newLength );
+ indices = Arrays.copyOf( indices, newLength );
+ }
+ distances[ size ] = distance;
+ indices[ size ] = index;
+ ++size;
+ }
+
+ void sort()
+ {
+ final int[] order = new int[ size ];
+ Arrays.setAll( order, i -> i );
+ quicksort( 0, size - 1, distances, order );
+ System.arraycopy( reorder( distances, order ), 0, distances, 0, size );
+ System.arraycopy( reorder( indices, order ), 0, indices, 0, size );
+ }
+
+ void makeCopyOf( final Neighbors other )
+ {
+ distances = other.distances.clone();
+ indices = other.indices.clone();
+ size = other.size;
+ }
+ }
+}
diff --git a/src/main/java/net/imglib2/neighborsearch/KNearestNeighborSearchOnKDTree.java b/src/main/java/net/imglib2/neighborsearch/KNearestNeighborSearchOnKDTree.java
index 621fa6c79..4dcc426d2 100644
--- a/src/main/java/net/imglib2/neighborsearch/KNearestNeighborSearchOnKDTree.java
+++ b/src/main/java/net/imglib2/neighborsearch/KNearestNeighborSearchOnKDTree.java
@@ -34,10 +34,12 @@
package net.imglib2.neighborsearch;
-import net.imglib2.KDTree;
-import net.imglib2.KDTreeNode;
+import java.util.Arrays;
import net.imglib2.RealLocalizable;
import net.imglib2.Sampler;
+import net.imglib2.KDTree;
+import net.imglib2.KDTreeNode;
+import net.imglib2.kdtree.KNearestNeighborSearchImpl;
/**
* Implementation of {@link KNearestNeighborSearch} search for kd-trees.
@@ -46,35 +48,38 @@
*/
public class KNearestNeighborSearchOnKDTree< T > implements KNearestNeighborSearch< T >
{
- protected KDTree< T > tree;
-
- protected final int n;
+ private final KDTree< T > tree;
- protected final double[] pos;
+ private final int k;
- protected final int k;
+ private final KNearestNeighborSearchImpl impl;
- protected KDTreeNode< T >[] bestPoints;
-
- protected double[] bestSquDistances;
+ private final KDTreeNode< T >[] matches;
@SuppressWarnings( "unchecked" )
public KNearestNeighborSearchOnKDTree( final KDTree< T > tree, final int k )
{
this.tree = tree;
- this.n = tree.numDimensions();
- this.pos = new double[ n ];
this.k = k;
- this.bestPoints = new KDTreeNode[ k ];
- this.bestSquDistances = new double[ k ];
- for ( int i = 0; i < k; ++i )
- bestSquDistances[ i ] = Double.MAX_VALUE;
+ impl = new KNearestNeighborSearchImpl( tree.impl(), k );
+ matches = new KDTreeNode[ k ];
+ Arrays.setAll( matches, i -> tree.createNode() );
+ }
+
+ @SuppressWarnings( "unchecked" )
+ private KNearestNeighborSearchOnKDTree( final KNearestNeighborSearchOnKDTree< T > knn )
+ {
+ tree = knn.tree;
+ k = knn.k;
+ impl = knn.impl.copy();
+ matches = new KDTreeNode[ k ];
+ Arrays.setAll( matches, i -> tree.createNode().setNodeIndex( impl.bestIndex( i ) ) );
}
@Override
public int numDimensions()
{
- return n;
+ return tree.numDimensions();
}
@Override
@@ -84,67 +89,35 @@ public int getK()
}
@Override
- public void search( final RealLocalizable reference )
+ public void search( final RealLocalizable p )
{
- reference.localize( pos );
- for ( int i = 0; i < k; ++i )
- bestSquDistances[ i ] = Double.MAX_VALUE;
- searchNode( tree.getRoot() );
- }
-
- protected void searchNode( final KDTreeNode< T > current )
- {
- // consider the current node
- final double squDistance = current.squDistanceTo( pos );
- if ( squDistance < bestSquDistances[ k - 1 ] )
- {
- int i = k - 1;
- for ( int j = i - 1; i > 0 && squDistance < bestSquDistances[ j ]; --i, --j )
- {
- bestSquDistances[ i ] = bestSquDistances[ j ];
- bestPoints[ i ] = bestPoints[ j ];
- }
- bestSquDistances[ i ] = squDistance;
- bestPoints[ i ] = current;
- }
-
- final double axisDiff = pos[ current.getSplitDimension() ] - current.getSplitCoordinate();
- final double axisSquDistance = axisDiff * axisDiff;
- final boolean leftIsNearBranch = axisDiff < 0;
-
- // search the near branch
- final KDTreeNode< T > nearChild = leftIsNearBranch ? current.left : current.right;
- final KDTreeNode< T > awayChild = leftIsNearBranch ? current.right : current.left;
- if ( nearChild != null )
- searchNode( nearChild );
-
- // search the away branch - maybe
- if ( ( axisSquDistance <= bestSquDistances[ k - 1 ] ) && ( awayChild != null ) )
- searchNode( awayChild );
+ impl.search( p );
+ for ( int i = 0; i < k; i++ )
+ matches[ i ].setNodeIndex( impl.bestIndex( i ) );
}
@Override
public Sampler< T > getSampler( final int i )
{
- return bestPoints[ i ];
+ return matches[ i ];
}
@Override
public RealLocalizable getPosition( final int i )
{
- return bestPoints[ i ];
+ return matches[ i ];
}
@Override
public double getSquareDistance( final int i )
{
- return bestSquDistances[ i ];
+ return impl.bestSquDistance( i );
}
@Override
- public double getDistance( final int i )
+ public KNearestNeighborSearchOnKDTree< T > copy()
{
- return Math.sqrt( bestSquDistances[ i ] );
+ return new KNearestNeighborSearchOnKDTree<>( this );
}
/* NearestNeighborSearch */
@@ -172,17 +145,4 @@ public double getDistance()
{
return getDistance( 0 );
}
-
- @Override
- public KNearestNeighborSearchOnKDTree< T > copy()
- {
- final KNearestNeighborSearchOnKDTree< T > copy = new KNearestNeighborSearchOnKDTree< T >( tree, k );
- System.arraycopy( pos, 0, copy.pos, 0, pos.length );
- for ( int i = 0; i < k; ++i )
- {
- copy.bestPoints[ i ] = bestPoints[ i ];
- copy.bestSquDistances[ i ] = bestSquDistances[ i ];
- }
- return copy;
- }
}
diff --git a/src/main/java/net/imglib2/neighborsearch/NearestNeighborSearchOnKDTree.java b/src/main/java/net/imglib2/neighborsearch/NearestNeighborSearchOnKDTree.java
index ce8980b1e..d94514d5b 100644
--- a/src/main/java/net/imglib2/neighborsearch/NearestNeighborSearchOnKDTree.java
+++ b/src/main/java/net/imglib2/neighborsearch/NearestNeighborSearchOnKDTree.java
@@ -34,10 +34,11 @@
package net.imglib2.neighborsearch;
-import net.imglib2.KDTree;
-import net.imglib2.KDTreeNode;
import net.imglib2.RealLocalizable;
import net.imglib2.Sampler;
+import net.imglib2.KDTree;
+import net.imglib2.KDTreeNode;
+import net.imglib2.kdtree.NearestNeighborSearchImpl;
/**
* Implementation of {@link NearestNeighborSearch} search for kd-trees.
@@ -47,60 +48,38 @@
*/
public class NearestNeighborSearchOnKDTree< T > implements NearestNeighborSearch< T >
{
- protected KDTree< T > tree;
-
- protected final int n;
+ private final KDTree< T > tree;
- protected final double[] pos;
+ private final NearestNeighborSearchImpl impl;
- protected KDTreeNode< T > bestPoint;
-
- protected double bestSquDistance;
+ private final KDTreeNode< T > bestPoint;
public NearestNeighborSearchOnKDTree( final KDTree< T > tree )
{
- n = tree.numDimensions();
- pos = new double[ n ];
this.tree = tree;
+ impl = new NearestNeighborSearchImpl( tree.impl() );
+ bestPoint = tree.createNode();
}
- @Override
- public int numDimensions()
+ private NearestNeighborSearchOnKDTree( final NearestNeighborSearchOnKDTree< T > nn )
{
- return n;
+ tree = nn.tree;
+ impl = nn.impl.copy();
+ bestPoint = tree.createNode();
+ bestPoint.setNodeIndex( nn.impl.bestIndex() );
}
@Override
- public void search( final RealLocalizable p )
+ public int numDimensions()
{
- p.localize( pos );
- bestSquDistance = Double.MAX_VALUE;
- searchNode( tree.getRoot() );
+ return tree.numDimensions();
}
- protected void searchNode( final KDTreeNode< T > current )
+ @Override
+ public void search( final RealLocalizable p )
{
- // consider the current node
- final double distance = current.squDistanceTo( pos );
- if ( distance < bestSquDistance )
- {
- bestSquDistance = distance;
- bestPoint = current;
- }
-
- final double axisDiff = pos[ current.getSplitDimension() ] - current.getSplitCoordinate();
- final double axisSquDistance = axisDiff * axisDiff;
- final boolean leftIsNearBranch = axisDiff < 0;
-
- // search the near branch
- final KDTreeNode< T > nearChild = leftIsNearBranch ? current.left : current.right;
- final KDTreeNode< T > awayChild = leftIsNearBranch ? current.right : current.left;
- if ( nearChild != null )
- searchNode( nearChild );
-
- // search the away branch - maybe
- if ( ( axisSquDistance <= bestSquDistance ) && ( awayChild != null ) )
- searchNode( awayChild );
+ impl.search( p );
+ bestPoint.setNodeIndex( impl.bestIndex() );
}
@Override
@@ -118,22 +97,12 @@ public RealLocalizable getPosition()
@Override
public double getSquareDistance()
{
- return bestSquDistance;
- }
-
- @Override
- public double getDistance()
- {
- return Math.sqrt( bestSquDistance );
+ return impl.bestSquDistance();
}
@Override
public NearestNeighborSearchOnKDTree< T > copy()
{
- final NearestNeighborSearchOnKDTree< T > copy = new NearestNeighborSearchOnKDTree< T >( tree );
- System.arraycopy( pos, 0, copy.pos, 0, pos.length );
- copy.bestPoint = bestPoint;
- copy.bestSquDistance = bestSquDistance;
- return copy;
+ return new NearestNeighborSearchOnKDTree<>( this );
}
}
diff --git a/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearchOnKDTree.java b/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearchOnKDTree.java
index 909b5402e..4fd670808 100644
--- a/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearchOnKDTree.java
+++ b/src/main/java/net/imglib2/neighborsearch/RadiusNeighborSearchOnKDTree.java
@@ -35,124 +35,90 @@
package net.imglib2.neighborsearch;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-
-import net.imglib2.KDTree;
-import net.imglib2.KDTreeNode;
+import java.util.List;
import net.imglib2.RealLocalizable;
import net.imglib2.Sampler;
-import net.imglib2.util.ValuePair;
+import net.imglib2.KDTree;
+import net.imglib2.KDTreeNode;
+import net.imglib2.kdtree.RadiusNeighborSearchImpl;
/**
* Implementation of {@link RadiusNeighborSearch} search for kd-trees.
- *
+ *
* @author Tobias Pietzsch
*/
public class RadiusNeighborSearchOnKDTree< T > implements RadiusNeighborSearch< T >
{
- protected KDTree< T > tree;
+ private final KDTree< T > tree;
- protected final int n;
+ private final RadiusNeighborSearchImpl impl;
- protected final double[] pos;
-
- protected ArrayList< ValuePair< KDTreeNode< T >, Double > > resultPoints;
+ private final List< KDTreeNode< T > > matches;
public RadiusNeighborSearchOnKDTree( final KDTree< T > tree )
{
this.tree = tree;
- this.n = tree.numDimensions();
- this.pos = new double[ n ];
- this.resultPoints = new ArrayList< ValuePair< KDTreeNode< T >, Double > >();
+ impl = new RadiusNeighborSearchImpl( tree.impl() );
+ matches = new ArrayList<>();
}
- @Override
- public void search( final RealLocalizable reference, final double radius, final boolean sortResults )
+ private RadiusNeighborSearchOnKDTree( final RadiusNeighborSearchOnKDTree< T > other )
{
- assert radius >= 0;
- reference.localize( pos );
- resultPoints.clear();
- searchNode( tree.getRoot(), radius * radius );
- if ( sortResults )
- {
- Collections.sort( resultPoints, new Comparator< ValuePair< KDTreeNode< T >, Double > >()
- {
- @Override
- public int compare( final ValuePair< KDTreeNode< T >, Double > o1, final ValuePair< KDTreeNode< T >, Double > o2 )
- {
- return Double.compare( o1.b, o2.b );
- }
- } );
- }
+ tree = other.tree;
+ impl = other.impl.copy();
+ matches = new ArrayList<>();
+ for ( final KDTreeNode< T > match : other.matches )
+ matches.add( tree.createNode().setNodeIndex( match.nodeIndex() ) );
}
@Override
- public int numDimensions()
+ public void search( final RealLocalizable reference, final double radius, final boolean sortResults )
{
- return n;
+ impl.search( reference, radius, sortResults );
+ fillMatches( 0, impl.numNeighbors() - 1 );
}
- protected void searchNode( final KDTreeNode< T > current, final double squRadius )
+ private void fillMatches( final int first, final int last )
{
- // consider the current node
- final double squDistance = current.squDistanceTo( pos );
- if ( squDistance <= squRadius )
- {
- resultPoints.add( new ValuePair< KDTreeNode< T >, Double >( current, squDistance ) );
- }
-
- final double axisDiff = pos[ current.getSplitDimension() ] - current.getSplitCoordinate();
- final double axisSquDistance = axisDiff * axisDiff;
- final boolean leftIsNearBranch = axisDiff < 0;
-
- // search the near branch
- final KDTreeNode< T > nearChild = leftIsNearBranch ? current.left : current.right;
- final KDTreeNode< T > awayChild = leftIsNearBranch ? current.right : current.left;
- if ( nearChild != null )
- searchNode( nearChild, squRadius );
+ while ( matches.size() < last + 1 )
+ matches.add( tree.createNode() );
+ for ( int i = 0; i <= last; ++i )
+ matches.get( i ).setNodeIndex( impl.bestIndex( i ) );
+ }
- // search the away branch - maybe
- if ( ( axisSquDistance <= squRadius ) && ( awayChild != null ) )
- searchNode( awayChild, squRadius );
+ @Override
+ public int numDimensions()
+ {
+ return tree.numDimensions();
}
@Override
public int numNeighbors()
{
- return resultPoints.size();
+ return impl.numNeighbors();
}
@Override
public Sampler< T > getSampler( final int i )
{
- return resultPoints.get( i ).a;
+ return matches.get( i );
}
@Override
public RealLocalizable getPosition( final int i )
{
- return resultPoints.get( i ).a;
+ return matches.get( i );
}
@Override
public double getSquareDistance( final int i )
{
- return resultPoints.get( i ).b;
- }
-
- @Override
- public double getDistance( final int i )
- {
- return Math.sqrt( resultPoints.get( i ).b );
+ return impl.bestSquDistance( i );
}
@Override
public RadiusNeighborSearchOnKDTree< T > copy()
{
- final RadiusNeighborSearchOnKDTree< T > copy = new RadiusNeighborSearchOnKDTree<>( tree );
- System.arraycopy( pos, 0, copy.pos, 0, pos.length );
- copy.resultPoints.addAll( resultPoints );
- return copy;
+ return new RadiusNeighborSearchOnKDTree<>( this );
}
}
diff --git a/src/test/java/net/imglib2/kdtree/KDTreeBenchmark.java b/src/test/java/net/imglib2/kdtree/KDTreeBenchmark.java
new file mode 100644
index 000000000..79c4e8fdd
--- /dev/null
+++ b/src/test/java/net/imglib2/kdtree/KDTreeBenchmark.java
@@ -0,0 +1,186 @@
+package net.imglib2.kdtree;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import net.imglib2.KDTree;
+import net.imglib2.RealLocalizable;
+import net.imglib2.RealPoint;
+import net.imglib2.neighborsearch.NearestNeighborSearchOnKDTree;
+import net.imglib2.neighborsearch.KNearestNeighborSearchOnKDTree;
+import net.imglib2.neighborsearch.RadiusNeighborSearchOnKDTree;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+import org.openjdk.jmh.annotations.Setup;
+
+@State( Scope.Benchmark )
+public class KDTreeBenchmark
+{
+// @Param({"3"})
+// public int n;
+//
+// @Param({"10000", "100000", "1000000"})
+// public int numDataVertices;
+//
+// @Param({"1000"})
+// public int numTestVertices;
+//
+ public int n = 3;
+ public int k = 10;
+ public int radius = 1;
+ public int numDataVertices = 100000;
+ public int numTestVertices = 1000;
+ public double minCoordinateValue = -5;
+ public double maxCoordinateValue = 5;
+
+ List< RealPoint > dataVertices;
+ List< RealPoint > testVertices;
+
+ private KDTree< RealPoint > kdtree;
+
+ @Setup
+ public void setup()
+ {
+ createVertices();
+// createVerticesSeqTest();
+ kdtree = new KDTree<>( dataVertices, dataVertices );
+// spoil();
+ }
+
+ public void spoil() {
+ final double[][] points = KDTreeUtils.initPositions( n, numDataVertices, dataVertices );
+ final int[] tree = KDTreeUtils.makeTree( points );
+ final double[][] treePoints = KDTreeUtils.reorder( points, tree );
+ final KDTreeImpl impl = new KDTreeImpl.Nested( treePoints );
+ final NearestNeighborSearchImpl search = new NearestNeighborSearchImpl( impl );
+ for ( RealPoint testVertex : testVertices )
+ search.search( testVertex );
+ }
+
+ @Benchmark
+ @BenchmarkMode( Mode.AverageTime )
+ @OutputTimeUnit( TimeUnit.MILLISECONDS )
+ public void createKDTree()
+ {
+ new KDTree<>( dataVertices, dataVertices );
+ }
+
+ @Benchmark
+ @BenchmarkMode( Mode.AverageTime )
+ @OutputTimeUnit( TimeUnit.MILLISECONDS )
+ public void nearestNeighborSearch()
+ {
+ final NearestNeighborSearchOnKDTree< RealPoint > kd = new NearestNeighborSearchOnKDTree<>( kdtree );
+ for ( final RealLocalizable t : testVertices )
+ {
+ kd.search( t );
+ kd.getSampler().get();
+ }
+ }
+
+ @Benchmark
+ @BenchmarkMode( Mode.AverageTime )
+ @OutputTimeUnit( TimeUnit.MILLISECONDS )
+ public void kNearestNeighborSearch()
+ {
+ final KNearestNeighborSearchOnKDTree< RealPoint > kd = new KNearestNeighborSearchOnKDTree<>( kdtree, k );
+ for ( final RealLocalizable t : testVertices )
+ {
+ kd.search( t );
+ kd.getSampler().get();
+// for ( int i = 0; i < k; i++ )
+// {
+// kd.getSampler( i ).get();
+// }
+ }
+ }
+
+ @Benchmark
+ @BenchmarkMode( Mode.AverageTime )
+ @OutputTimeUnit( TimeUnit.MILLISECONDS )
+ public void radiusNeighborSearch()
+ {
+ final RadiusNeighborSearchOnKDTree< RealPoint > kd = new RadiusNeighborSearchOnKDTree<>( kdtree );
+ for ( final RealLocalizable t : testVertices )
+ {
+ kd.search( t, radius, true );
+ for ( int i = 0; i < Math.min( kd.numNeighbors(), k ); i++ )
+ {
+ kd.getSampler( i ).get();
+ }
+ }
+ }
+
+ private void createVertices()
+ {
+ final double[] p = new double[ n ];
+ final double size = ( maxCoordinateValue - minCoordinateValue );
+ final Random rnd = new Random( 4379 );
+ dataVertices = new ArrayList<>();
+ for ( int i = 0; i < numDataVertices; ++i )
+ {
+ for ( int d = 0; d < n; ++d )
+ p[ d ] = rnd.nextDouble() * size + minCoordinateValue;
+ dataVertices.add( new RealPoint( p ) );
+ }
+ testVertices = new ArrayList<>();
+ for ( int i = 0; i < numTestVertices; ++i )
+ {
+ for ( int d = 0; d < n; ++d )
+ p[ d ] = rnd.nextDouble() * 2 * size + minCoordinateValue - size / 2;
+ testVertices.add( new RealPoint( p ) );
+ }
+ }
+
+ private void createVerticesSeqTest()
+ {
+ final double[] p = new double[ n ];
+ final double size = ( maxCoordinateValue - minCoordinateValue );
+ final Random rnd = new Random( 4379 );
+ dataVertices = new ArrayList<>();
+ for ( int i = 0; i < numDataVertices; ++i )
+ {
+ for ( int d = 0; d < n; ++d )
+ p[ d ] = rnd.nextDouble() * size + minCoordinateValue;
+ dataVertices.add( new RealPoint( p ) );
+ }
+ testVertices = new ArrayList<>();
+ for ( int i = 0; i < numTestVertices; ++i )
+ {
+ if ( rnd.nextDouble() < 0.8 )
+ {
+ int d = rnd.nextInt( n );
+ p[ d ] += size / 10000.0;
+ }
+ else
+ {
+ for ( int d = 0; d < n; ++d )
+ p[ d ] = rnd.nextDouble() * 2 * size + minCoordinateValue - size / 2;
+ }
+ testVertices.add( new RealPoint( p ) );
+ }
+ }
+
+ public static void main( final String... args ) throws RunnerException
+ {
+ final Options opt = new OptionsBuilder()
+ .include( KDTreeBenchmark.class.getSimpleName() )
+ .forks( 0 )
+ .warmupIterations( 4 )
+ .measurementIterations( 8 )
+ .warmupTime( TimeValue.milliseconds( 500 ) )
+ .measurementTime( TimeValue.milliseconds( 500 ) )
+ .build();
+ new Runner( opt ).run();
+ }
+}
diff --git a/src/test/java/net/imglib2/kdtree/KDTreeImplTest.java b/src/test/java/net/imglib2/kdtree/KDTreeImplTest.java
new file mode 100644
index 000000000..d3095f98f
--- /dev/null
+++ b/src/test/java/net/imglib2/kdtree/KDTreeImplTest.java
@@ -0,0 +1,151 @@
+package net.imglib2.kdtree;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import net.imglib2.RealLocalizable;
+import net.imglib2.RealPoint;
+import net.imglib2.util.LinAlgHelpers;
+import org.junit.Assert;
+import org.junit.Before;
+
+import java.util.ArrayList;
+import java.util.Random;
+import org.junit.Test;
+
+public class KDTreeImplTest {
+
+ public int n = 3;
+ public int numDataVertices = 100;
+ public int numTestVertices = 10;
+ public double minCoordinateValue = -5;
+ public double maxCoordinateValue = 5;
+
+ public List< RealPoint > dataVertices;
+ public List< RealPoint > testVertices;
+
+ @Before
+ public void init()
+ {
+ final double[] p = new double[ n ];
+ final double size = ( maxCoordinateValue - minCoordinateValue );
+ final Random rnd = new Random( 4379 );
+ dataVertices = new ArrayList<>();
+ for ( int i = 0; i < numDataVertices; ++i )
+ {
+ for ( int d = 0; d < n; ++d )
+ p[ d ] = rnd.nextDouble() * size + minCoordinateValue;
+ dataVertices.add( new RealPoint( p ) );
+ }
+ testVertices = new ArrayList<>();
+ for ( int i = 0; i < numTestVertices; ++i )
+ {
+ for ( int d = 0; d < n; ++d )
+ p[ d ] = rnd.nextDouble() * 2 * size + minCoordinateValue - size / 2;
+ testVertices.add( new RealPoint( p ) );
+ }
+ }
+
+ @Test
+ public void testNearestNeighborSearch()
+ {
+ final double[][] points = KDTreeUtils.initPositions( n, numDataVertices, dataVertices );
+ final int[] tree = KDTreeUtils.makeTree( points );
+ final double[][] treePoints = KDTreeUtils.reorder( points, tree );
+ final KDTreeImpl impl = new KDTreeImpl.Nested( treePoints );
+ final NearestNeighborSearchImpl search = new NearestNeighborSearchImpl( impl );
+
+ for ( RealPoint testVertex : testVertices )
+ {
+ final int expected = findNearestNeighborExhaustive( testVertex );
+
+ search.search( testVertex );
+ final int actual = tree[ search.bestIndex() ];
+
+// System.out.println( "actual = " + actual + ", expected = " + expected );
+ Assert.assertEquals( expected, actual );
+ }
+ }
+
+ @Test
+ public void testKNearestNeighborSearch()
+ {
+ final int k = 10;
+
+ final double[][] points = KDTreeUtils.initPositions( n, numDataVertices, dataVertices );
+ final int[] tree = KDTreeUtils.makeTree( points );
+ final double[][] treePoints = KDTreeUtils.reorder( points, tree );
+ final KDTreeImpl impl = new KDTreeImpl.Nested( treePoints );
+ final KNearestNeighborSearchImpl search = new KNearestNeighborSearchImpl( impl, k );
+
+ for ( RealPoint testVertex : testVertices )
+ {
+ final int[] expecteds = findNearestNeighborsExhaustive( testVertex, k );
+
+ search.search( testVertex );
+ final int[] actuals = new int[ k ];
+ Arrays.setAll( actuals, i -> tree[ search.bestIndex( i ) ] );
+
+// System.out.println( "actual = " + actual + ", expected = " + expected );
+ Assert.assertArrayEquals( expecteds, actuals );
+ }
+ }
+
+ @Test
+ public void testRadiusNeighborSearch()
+ {
+ final double radius = 7;
+
+ final double[][] points = KDTreeUtils.initPositions( n, numDataVertices, dataVertices );
+ final int[] tree = KDTreeUtils.makeTree( points );
+ final double[][] treePoints = KDTreeUtils.reorder( points, tree );
+ final KDTreeImpl impl = new KDTreeImpl.Nested( treePoints );
+ final RadiusNeighborSearchImpl search = new RadiusNeighborSearchImpl( impl );
+
+ for ( RealPoint testVertex : testVertices )
+ {
+ final int[] expecteds = findRadiusNeighborsExhaustive( testVertex, radius );
+
+ search.search( testVertex, radius, true );
+ final int[] actuals = new int[ search.numNeighbors() ];
+ Arrays.setAll( actuals, i -> tree[ search.bestIndex( i ) ] );
+
+ Assert.assertArrayEquals( expecteds, actuals );
+ }
+ }
+
+ private int findNearestNeighborExhaustive( final RealLocalizable point )
+ {
+ return findNearestNeighborsExhaustive( point, 1 )[ 0 ];
+ }
+
+ private int[] findNearestNeighborsExhaustive( final RealLocalizable point, final int k )
+ {
+ final List< RealPoint > sorted = new ArrayList<>();
+ sorted.addAll( dataVertices );
+ sorted.sort( Comparator.comparing( p -> distance( point, p ) ) );
+ final int[] neighbors = new int[ k ];
+ Arrays.setAll(neighbors, i -> dataVertices.indexOf( sorted.get( i ) ) );
+ return neighbors;
+ }
+
+ private static double distance( final RealLocalizable p1, final RealLocalizable p2 )
+ {
+ return LinAlgHelpers.distance( p2.positionAsDoubleArray(), p1.positionAsDoubleArray() );
+ }
+
+ private int[] findRadiusNeighborsExhaustive( final RealLocalizable point, final double radius )
+ {
+ final List< RealPoint > sorted = new ArrayList<>();
+ dataVertices.forEach( p -> {
+ if ( distance( point, p ) <= radius )
+ {
+ sorted.add( p );
+ }
+ } );
+ sorted.sort( Comparator.comparing( p -> distance( point, p ) ) );
+ final int[] neighbors = new int[ sorted.size() ];
+ Arrays.setAll(neighbors, i -> dataVertices.indexOf( sorted.get( i ) ) );
+ return neighbors;
+ }
+}
From 17a51f8db86f49d4920cfdf81d15aab84d96c761 Mon Sep 17 00:00:00 2001
From: tpietzsch
Date: Sat, 15 Jul 2023 00:07:35 +0200
Subject: [PATCH 05/13] Replace explicit type arguments with <>
---
.../imglib2/nearestneighbor/KDTreeTest.java | 28 +++++++++----------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/src/test/java/net/imglib2/nearestneighbor/KDTreeTest.java b/src/test/java/net/imglib2/nearestneighbor/KDTreeTest.java
index 5ff8cb5a5..c244adf78 100644
--- a/src/test/java/net/imglib2/nearestneighbor/KDTreeTest.java
+++ b/src/test/java/net/imglib2/nearestneighbor/KDTreeTest.java
@@ -58,7 +58,7 @@ public class KDTreeTest
{
protected static boolean testNearestNeighbor( final int numDimensions, final int numPoints, final int numTests, final float min, final float max )
{
- final ArrayList< RealPoint > points = new ArrayList< RealPoint >();
+ final ArrayList< RealPoint > points = new ArrayList<>();
final Random rnd = new Random( 435435435 );
final float[] p = new float[ numDimensions ];
@@ -75,13 +75,13 @@ protected static boolean testNearestNeighbor( final int numDimensions, final int
}
long start = System.currentTimeMillis();
- final KDTree< RealPoint > kdTree = new KDTree< RealPoint >( points, points );
- final NearestNeighborSearchOnKDTree< RealPoint > kd = new NearestNeighborSearchOnKDTree< RealPoint >( kdTree );
+ final KDTree< RealPoint > kdTree = new KDTree<>( points, points );
+ final NearestNeighborSearchOnKDTree< RealPoint > kd = new NearestNeighborSearchOnKDTree<>( kdTree );
final long kdSetupTime = System.currentTimeMillis() - start;
System.out.println( "kdtree setup took: " + ( kdSetupTime ) + " ms." );
start = System.currentTimeMillis();
- final ArrayList< RealPoint > testpoints = new ArrayList< RealPoint >();
+ final ArrayList< RealPoint > testpoints = new ArrayList<>();
for ( int i = 0; i < numTests; ++i )
{
for ( int d = 0; d < numDimensions; ++d )
@@ -163,7 +163,7 @@ private static RealPoint findNearestNeighborExhaustive( final ArrayList< RealPoi
protected static boolean testKNearestNeighbor( final int neighbors, final int numDimensions, final int numPoints, final int numTests, final float min, final float max )
{
- final ArrayList< RealPoint > points = new ArrayList< RealPoint >();
+ final ArrayList< RealPoint > points = new ArrayList<>();
final Random rnd = new Random( 435435435 );
final float[] p = new float[ numDimensions ];
@@ -180,13 +180,13 @@ protected static boolean testKNearestNeighbor( final int neighbors, final int nu
}
long start = System.currentTimeMillis();
- final KDTree< RealPoint > kdTree = new KDTree< RealPoint >( points, points );
- final KNearestNeighborSearchOnKDTree< RealPoint > kd = new KNearestNeighborSearchOnKDTree< RealPoint >( kdTree, neighbors );
+ final KDTree< RealPoint > kdTree = new KDTree<>( points, points );
+ final KNearestNeighborSearchOnKDTree< RealPoint > kd = new KNearestNeighborSearchOnKDTree<>( kdTree, neighbors );
final long kdSetupTime = System.currentTimeMillis() - start;
System.out.println( "kdtree setup took: " + ( kdSetupTime ) + " ms." );
start = System.currentTimeMillis();
- final ArrayList< RealPoint > testpoints = new ArrayList< RealPoint >();
+ final ArrayList< RealPoint > testpoints = new ArrayList<>();
for ( int i = 0; i < numTests; ++i )
{
for ( int d = 0; d < numDimensions; ++d )
@@ -287,7 +287,7 @@ private static RealPoint[] findKNearestNeighborExhaustive( final ArrayList< Real
protected static boolean testRadiusNeighbor( final int numDimensions, final int numPoints, final int numTests, final float min, final float max )
{
- final ArrayList< RealPoint > points = new ArrayList< RealPoint >();
+ final ArrayList< RealPoint > points = new ArrayList<>();
final Random rnd = new Random( 435435435 );
final float[] p = new float[ numDimensions ];
@@ -306,13 +306,13 @@ protected static boolean testRadiusNeighbor( final int numDimensions, final int
final double radius = rnd.nextDouble() * size / 10;
long start = System.currentTimeMillis();
- final KDTree< RealPoint > kdTree = new KDTree< RealPoint >( points, points );
- final RadiusNeighborSearchOnKDTree< RealPoint > kd = new RadiusNeighborSearchOnKDTree< RealPoint >( kdTree );
+ final KDTree< RealPoint > kdTree = new KDTree<>( points, points );
+ final RadiusNeighborSearchOnKDTree< RealPoint > kd = new RadiusNeighborSearchOnKDTree<>( kdTree );
final long kdSetupTime = System.currentTimeMillis() - start;
System.out.println( "kdtree setup took: " + ( kdSetupTime ) + " ms." );
start = System.currentTimeMillis();
- final ArrayList< RealPoint > testpoints = new ArrayList< RealPoint >();
+ final ArrayList< RealPoint > testpoints = new ArrayList<>();
for ( int i = 0; i < numTests; ++i )
{
for ( int d = 0; d < numDimensions; ++d )
@@ -384,7 +384,7 @@ protected static boolean testRadiusNeighbor( final int numDimensions, final int
private static ArrayList< ValuePair< RealPoint, Double > > findNeighborsRadiusExhaustive( final ArrayList< RealPoint > points, final RealPoint t, final double radius, final boolean sortResults )
{
- final ArrayList< ValuePair< RealPoint, Double > > withinRadius = new ArrayList< ValuePair< RealPoint, Double > >();
+ final ArrayList< ValuePair< RealPoint, Double > > withinRadius = new ArrayList<>();
final int n = t.numDimensions();
final float[] tpos = new float[ n ];
@@ -400,7 +400,7 @@ private static ArrayList< ValuePair< RealPoint, Double > > findNeighborsRadiusEx
dist = Math.sqrt( dist );
if ( dist <= radius )
- withinRadius.add( new ValuePair< RealPoint, Double >( p, dist ) );
+ withinRadius.add( new ValuePair<>( p, dist ) );
}
if ( sortResults )
From 053d8023c9cbf5658fb0bf0bd84b6e514b703aa6 Mon Sep 17 00:00:00 2001
From: tpietzsch
Date: Fri, 14 Jul 2023 22:58:59 +0200
Subject: [PATCH 06/13] Move neighbor search related tests to
net,imglib2.neighborsearch
---
.../imglib2/{nearestneighbor => neighborsearch}/KDTreeTest.java | 2 +-
.../NearestNeighborSearchOnIterableRealIntervalTest.java | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
rename src/test/java/net/imglib2/{nearestneighbor => neighborsearch}/KDTreeTest.java (99%)
rename src/test/java/net/imglib2/{nearestneighbor => neighborsearch}/NearestNeighborSearchOnIterableRealIntervalTest.java (99%)
diff --git a/src/test/java/net/imglib2/nearestneighbor/KDTreeTest.java b/src/test/java/net/imglib2/neighborsearch/KDTreeTest.java
similarity index 99%
rename from src/test/java/net/imglib2/nearestneighbor/KDTreeTest.java
rename to src/test/java/net/imglib2/neighborsearch/KDTreeTest.java
index c244adf78..0d9fb8d08 100644
--- a/src/test/java/net/imglib2/nearestneighbor/KDTreeTest.java
+++ b/src/test/java/net/imglib2/neighborsearch/KDTreeTest.java
@@ -32,7 +32,7 @@
* #L%
*/
-package net.imglib2.nearestneighbor;
+package net.imglib2.neighborsearch;
import static org.junit.Assert.assertTrue;
diff --git a/src/test/java/net/imglib2/nearestneighbor/NearestNeighborSearchOnIterableRealIntervalTest.java b/src/test/java/net/imglib2/neighborsearch/NearestNeighborSearchOnIterableRealIntervalTest.java
similarity index 99%
rename from src/test/java/net/imglib2/nearestneighbor/NearestNeighborSearchOnIterableRealIntervalTest.java
rename to src/test/java/net/imglib2/neighborsearch/NearestNeighborSearchOnIterableRealIntervalTest.java
index ab05d1444..9e82adca7 100644
--- a/src/test/java/net/imglib2/nearestneighbor/NearestNeighborSearchOnIterableRealIntervalTest.java
+++ b/src/test/java/net/imglib2/neighborsearch/NearestNeighborSearchOnIterableRealIntervalTest.java
@@ -32,7 +32,7 @@
* #L%
*/
-package net.imglib2.nearestneighbor;
+package net.imglib2.neighborsearch;
import static org.junit.Assert.assertTrue;
import net.imglib2.RealCursor;
From 72b7e9c8bac8fef4e9d200555162003d11511b9c Mon Sep 17 00:00:00 2001
From: tpietzsch
Date: Sat, 15 Jul 2023 00:47:25 +0200
Subject: [PATCH 07/13] fix javadoc error
---
src/main/java/net/imglib2/kdtree/KDTreeData.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeData.java b/src/main/java/net/imglib2/kdtree/KDTreeData.java
index 761fea25a..ca65d42ac 100644
--- a/src/main/java/net/imglib2/kdtree/KDTreeData.java
+++ b/src/main/java/net/imglib2/kdtree/KDTreeData.java
@@ -210,7 +210,7 @@ public Supplier< IntFunction< T > > valuesSupplier()
/**
* Get positions of points in the tree as a flat {@code double[]} where
- * @code positions[d + i*n]} is dimension {@code d} of the {@code i}-th
+ * {@code positions[d + i*n]} is dimension {@code d} of the {@code i}-th
* point, with {@code n} the number of dimensions.
*
* For serialisation and usage by the tree.
From 720267b1a72c39bb9e38939590d2f7bb6bda5785 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Thu, 10 Aug 2023 16:54:34 -0400
Subject: [PATCH 08/13] Carve out values from KDTreeData
---
.../java/net/imglib2/kdtree/KDTreeData.java | 46 +++------------
.../java/net/imglib2/kdtree/KDTreeValues.java | 53 ++++++++++++++++++
.../net/imglib2/kdtree/KDTreeValuesImg.java | 56 +++++++++++++++++++
.../net/imglib2/kdtree/KDTreeValuesList.java | 51 +++++++++++++++++
4 files changed, 168 insertions(+), 38 deletions(-)
create mode 100644 src/main/java/net/imglib2/kdtree/KDTreeValues.java
create mode 100644 src/main/java/net/imglib2/kdtree/KDTreeValuesImg.java
create mode 100644 src/main/java/net/imglib2/kdtree/KDTreeValuesList.java
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeData.java b/src/main/java/net/imglib2/kdtree/KDTreeData.java
index ca65d42ac..7f8218577 100644
--- a/src/main/java/net/imglib2/kdtree/KDTreeData.java
+++ b/src/main/java/net/imglib2/kdtree/KDTreeData.java
@@ -59,11 +59,7 @@ public enum PositionsLayout
private final double[][] positions;
private final double[] flatPositions;
- private final List< T > valuesList;
- private final RandomAccessibleInterval< T > valuesImg;
- private final Supplier< IntFunction< T > > valuesSupplier;
-
- private final T type;
+ private final KDTreeValues values;
private volatile RealInterval boundingBox;
@@ -76,12 +72,7 @@ public KDTreeData( double[][] positions, List< T > values )
this.positions = positions;
flatPositions = null;
- valuesList = values;
- valuesImg = null;
- final IntFunction< T > v = values::get;
- valuesSupplier = () -> v;
-
- type = KDTreeUtils.getType( values );
+ this.values = new KDTreeValuesList<>(values);
}
public KDTreeData( double[][] positions, List< T > values, RealInterval boundingBox )
@@ -99,14 +90,7 @@ public KDTreeData( double[][] positions, RandomAccessibleInterval< T > values )
this.positions = positions;
flatPositions = null;
- valuesList = null;
- valuesImg = values;
- valuesSupplier = () -> {
- final RandomAccess ra = valuesImg.randomAccess();
- return i -> ra.setPositionAndGet(i);
- };
-
- type = Util.getTypeFromInterval( values );
+ this.values = new KDTreeValuesImg<>(values);
}
public KDTreeData( double[][] positions, RandomAccessibleInterval< T > values, RealInterval boundingBox )
@@ -124,12 +108,7 @@ public KDTreeData( double[] positions, List< T > values )
this.positions = null;
flatPositions = positions;
- valuesList = values;
- valuesImg = null;
- final IntFunction< T > v = values::get;
- valuesSupplier = () -> v;
-
- type = KDTreeUtils.getType( values );
+ this.values = new KDTreeValuesList<>(values);
}
public KDTreeData( double[] positions, List< T > values, RealInterval boundingBox )
@@ -147,14 +126,7 @@ public KDTreeData( double[] positions, RandomAccessibleInterval< T > values )
this.positions = null;
flatPositions = positions;
- valuesList = null;
- valuesImg = values;
- valuesSupplier = () -> {
- final RandomAccess ra = valuesImg.randomAccess();
- return i -> ra.setPositionAndGet(i);
- };
-
- type = Util.getTypeFromInterval( values );
+ this.values = new KDTreeValuesImg<>(values);
}
public KDTreeData( double[] positions, RandomAccessibleInterval< T > values, RealInterval boundingBox )
@@ -166,7 +138,7 @@ public KDTreeData( double[] positions, RandomAccessibleInterval< T > values, Rea
// TODO could also be Class instead? What is more useful?
public T type()
{
- return type;
+ return values.type();
}
/**
@@ -191,9 +163,7 @@ public double[][] positions()
*/
public RandomAccessibleInterval< T > values()
{
- return valuesImg != null
- ? valuesImg
- : ListImg.wrap( valuesList, size() );
+ return values.values();
}
/**
@@ -205,7 +175,7 @@ public RandomAccessibleInterval< T > values()
*/
public Supplier< IntFunction< T > > valuesSupplier()
{
- return valuesSupplier;
+ return values.valuesSupplier();
}
/**
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeValues.java b/src/main/java/net/imglib2/kdtree/KDTreeValues.java
new file mode 100644
index 000000000..ab66189ac
--- /dev/null
+++ b/src/main/java/net/imglib2/kdtree/KDTreeValues.java
@@ -0,0 +1,53 @@
+package net.imglib2.kdtree;
+
+import net.imglib2.RandomAccessibleInterval;
+
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+
+/**
+ * Stores the KDTree values.
+ *
+ * Values (of type {@code T}) are stored as either a 1D {@code
+ * RandomAccessibleInterval}, or a {@code List}. Individual values can be
+ * accessed by {@link #valuesSupplier()}{@code .get().apply(i)}. {@code
+ * valueSupplier().get()} returns a reusable {@code IntFunction}. Here {@code
+ * T} maybe a proxy that is reused in subsequent {@code apply(i)}.
+ *
+ * {@link #values()} returns all values as a 1D {@code
+ * RandomAccessibleInterval}. (If data is stored as {@code List}, it is
+ * wrapped into a {@code ListImg}.)
+ *
+ * @param
+ * the type of values stored in the tree.
+ */
+abstract public class KDTreeValues {
+ protected final T type;
+
+ public KDTreeValues(T type) {
+ this.type = type;
+ }
+
+ public T type() {
+ return type;
+ }
+
+ /**
+ * @return number of values in the tree
+ */
+ abstract public int size();
+
+ /**
+ * Get the values as a 1D {@code RandomAccessibleInterval}, for serialization.
+ */
+ abstract public RandomAccessibleInterval values();
+
+ /**
+ * Get a {@code Supplier} that return {@code IntFunction} to provide
+ * values for a given node indices. If the returned {@code IntFunction}
+ * is stateful ({@code T} maybe a proxy that is reused in subsequent {@code
+ * apply(i)}} every {@link Supplier#get()} creates a new instance of the
+ * {@code IntFunction}.
+ */
+ abstract public Supplier> valuesSupplier();
+}
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeValuesImg.java b/src/main/java/net/imglib2/kdtree/KDTreeValuesImg.java
new file mode 100644
index 000000000..569d8b03e
--- /dev/null
+++ b/src/main/java/net/imglib2/kdtree/KDTreeValuesImg.java
@@ -0,0 +1,56 @@
+package net.imglib2.kdtree;
+
+import net.imglib2.RandomAccess;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.img.list.ListImg;
+import net.imglib2.util.Util;
+
+import java.util.List;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+
+/**
+ * Stores KDTree values as 1D {@code RandomAccessibleInterval}.
+ *
+ * @param
+ * the type of values stored in the tree.
+ */
+public class KDTreeValuesImg extends KDTreeValues {
+
+ private final RandomAccessibleInterval values;
+ private final Supplier> valuesSupplier;
+
+ public KDTreeValuesImg(RandomAccessibleInterval values) {
+ super(Util.getTypeFromInterval(values));
+ this.values = values;
+ valuesSupplier = () -> {
+ final RandomAccess ra = values.randomAccess();
+ return ra::setPositionAndGet;
+ };
+ }
+
+ /**
+ * @return number of values in the tree
+ */
+ public int size() {
+ return (int) values.dimension(0);
+ }
+
+ /**
+ * Get the values as a 1D {@code RandomAccessibleInterval}, for serialization.
+ */
+ public RandomAccessibleInterval values() {
+ return values;
+ }
+
+ /**
+ * Get a {@code Supplier} that return {@code IntFunction} to provide
+ * values for a given node indices. If the returned {@code IntFunction}
+ * is stateful ({@code T} maybe a proxy that is reused in subsequent {@code
+ * apply(i)}} every {@link Supplier#get()} creates a new instance of the
+ * {@code IntFunction}.
+ */
+ public Supplier> valuesSupplier() {
+ return valuesSupplier;
+ }
+}
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeValuesList.java b/src/main/java/net/imglib2/kdtree/KDTreeValuesList.java
new file mode 100644
index 000000000..a76ab85a5
--- /dev/null
+++ b/src/main/java/net/imglib2/kdtree/KDTreeValuesList.java
@@ -0,0 +1,51 @@
+package net.imglib2.kdtree;
+
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.img.list.ListImg;
+
+import java.util.List;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+
+/**
+ * Stores KDTree values as List.
+ *
+ * @param
+ * the type of values stored in the tree.
+ */
+public class KDTreeValuesList extends KDTreeValues {
+
+ private final List values;
+ private final Supplier> valuesSupplier;
+
+ public KDTreeValuesList(List values) {
+ super(KDTreeUtils.getType(values));
+ this.values = values;
+ valuesSupplier = () -> values::get;
+ }
+
+ /**
+ * @return number of values in the tree
+ */
+ public int size() {
+ return values.size();
+ }
+
+ /**
+ * Get the values as a 1D {@code RandomAccessibleInterval}, for serialization.
+ */
+ public RandomAccessibleInterval values() {
+ return ListImg.wrap(values, values.size());
+ }
+
+ /**
+ * Get a {@code Supplier} that return {@code IntFunction} to provide
+ * values for a given node indices. If the returned {@code IntFunction}
+ * is stateful ({@code T} maybe a proxy that is reused in subsequent {@code
+ * apply(i)}} every {@link Supplier#get()} creates a new instance of the
+ * {@code IntFunction}.
+ */
+ public Supplier> valuesSupplier() {
+ return valuesSupplier;
+ }
+}
From ea26d26ffba98af25079135d02361972d7ed1f97 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Fri, 11 Aug 2023 15:09:17 -0400
Subject: [PATCH 09/13] Separate actual positions from tree implementation
---
.../java/net/imglib2/kdtree/KDTreeData.java | 5 ++
.../java/net/imglib2/kdtree/KDTreeImpl.java | 33 +++-----
.../net/imglib2/kdtree/KDTreePositions.java | 80 +++++++++++++++++++
.../net/imglib2/kdtree/KDTreeValuesImg.java | 3 +
.../net/imglib2/kdtree/KDTreeValuesList.java | 3 +
5 files changed, 102 insertions(+), 22 deletions(-)
create mode 100644 src/main/java/net/imglib2/kdtree/KDTreePositions.java
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeData.java b/src/main/java/net/imglib2/kdtree/KDTreeData.java
index 7f8218577..463ec804c 100644
--- a/src/main/java/net/imglib2/kdtree/KDTreeData.java
+++ b/src/main/java/net/imglib2/kdtree/KDTreeData.java
@@ -58,6 +58,7 @@ public enum PositionsLayout
private final PositionsLayout layout;
private final double[][] positions;
private final double[] flatPositions;
+ private final KDTreePositions newPositions;
private final KDTreeValues values;
@@ -71,6 +72,7 @@ public KDTreeData( double[][] positions, List< T > values )
layout = PositionsLayout.NESTED;
this.positions = positions;
flatPositions = null;
+ newPositions = new KDTreePositions.Nested(positions);
this.values = new KDTreeValuesList<>(values);
}
@@ -89,6 +91,7 @@ public KDTreeData( double[][] positions, RandomAccessibleInterval< T > values )
layout = PositionsLayout.NESTED;
this.positions = positions;
flatPositions = null;
+ newPositions = new KDTreePositions.Nested(positions);
this.values = new KDTreeValuesImg<>(values);
}
@@ -107,6 +110,7 @@ public KDTreeData( double[] positions, List< T > values )
layout = FLAT;
this.positions = null;
flatPositions = positions;
+ newPositions = new KDTreePositions.Flat(positions, numDimensions);
this.values = new KDTreeValuesList<>(values);
}
@@ -125,6 +129,7 @@ public KDTreeData( double[] positions, RandomAccessibleInterval< T > values )
layout = FLAT;
this.positions = null;
flatPositions = positions;
+ newPositions = new KDTreePositions.Flat(positions, numDimensions);
this.values = new KDTreeValuesImg<>(values);
}
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeImpl.java b/src/main/java/net/imglib2/kdtree/KDTreeImpl.java
index 2f4468e77..9c33cb9e5 100644
--- a/src/main/java/net/imglib2/kdtree/KDTreeImpl.java
+++ b/src/main/java/net/imglib2/kdtree/KDTreeImpl.java
@@ -47,37 +47,24 @@ public abstract class KDTreeImpl
private final int numPoints;
+ // TODO: make this final
+ protected KDTreePositions positions;
+
static class Nested extends KDTreeImpl
{
- private final double[][] positions;
-
Nested( final double[][] positions )
{
super( positions.length, positions[ 0 ].length );
- this.positions = positions;
- }
-
- @Override
- public double getDoublePosition( final int i, final int d )
- {
- return positions[ d ][ i ];
+ this.positions = new KDTreePositions.Nested(positions);
}
}
static class Flat extends KDTreeImpl
{
- private final double[] positions;
-
Flat( final double[] positions, final int numDimensions )
{
super( numDimensions, positions.length / numDimensions );
- this.positions = positions;
- }
-
- @Override
- public double getDoublePosition( final int i, final int d )
- {
- return positions[ numDimensions * i + d ];
+ this.positions = new KDTreePositions.Flat(positions, numDimensions);
}
}
@@ -158,7 +145,9 @@ public int splitDimension( final int i )
return ( 31 - Integer.numberOfLeadingZeros( i + 1 ) ) % numDimensions;
}
- public abstract double getDoublePosition( final int i, final int d );
+ public double getDoublePosition(final int i, final int d) {
+ return positions.get(i, d);
+ }
/**
* Compute the squared distance from node {@code i} to {@code pos}.
@@ -168,7 +157,7 @@ public float squDistance( final int i, final float[] pos )
float sum = 0;
for ( int d = 0; d < numDimensions; ++d )
{
- final float diff = pos[ d ] - ( float ) getDoublePosition( i, d );
+ final float diff = pos[ d ] - ( float ) positions.get(i, d);
sum += diff * diff;
}
return sum;
@@ -182,7 +171,7 @@ public double squDistance( final int i, final double[] pos )
double sum = 0;
for ( int d = 0; d < numDimensions; ++d )
{
- final double diff = pos[ d ] - getDoublePosition( i, d );
+ final double diff = pos[ d ] - positions.get(i, d);
sum += diff * diff;
}
return sum;
@@ -196,7 +185,7 @@ public double squDistance( final int i, final RealLocalizable pos )
double sum = 0;
for ( int d = 0; d < numDimensions; ++d )
{
- final double diff = pos.getDoublePosition( d ) - getDoublePosition( i, d );
+ final double diff = pos.getDoublePosition( d ) - positions.get(i, d);
sum += diff * diff;
}
return sum;
diff --git a/src/main/java/net/imglib2/kdtree/KDTreePositions.java b/src/main/java/net/imglib2/kdtree/KDTreePositions.java
new file mode 100644
index 000000000..86f3f8ef8
--- /dev/null
+++ b/src/main/java/net/imglib2/kdtree/KDTreePositions.java
@@ -0,0 +1,80 @@
+package net.imglib2.kdtree;
+
+import net.imglib2.RealLocalizable;
+
+import static net.imglib2.kdtree.KDTreeUtils.leftChildIndex;
+import static net.imglib2.kdtree.KDTreeUtils.parentIndex;
+import static net.imglib2.kdtree.KDTreeUtils.rightChildIndex;
+
+/**
+ * Stores the positions of the nodes in a KDTree and provides access to them.
+ *
+ * Currently, there are two implementations:
+ * - {@link Nested} stores the positions as a {@code double[][]}, allowing a
+ * total of 2^31-8 nodes but doesn't keep the positions contiguous in memory.
+ * - {@link Flat} stores the positions as a {@code double[]}, meaning that the
+ * positions are contiguous in memory but the number of nodes is limited to
+ * (2^31-8)/numDimensions.
+ */
+public abstract class KDTreePositions {
+
+ protected final int numDimensions;
+
+ protected final int numPoints;
+
+ static class Nested extends KDTreePositions {
+ private final double[][] positions;
+
+ Nested(final double[][] positions) {
+ super(positions.length, positions[0].length);
+ this.positions = positions;
+ }
+
+ @Override
+ public double get(final int i, final int d) {
+ return positions[d][i];
+ }
+ }
+
+ static class Flat extends KDTreePositions {
+ private final double[] positions;
+
+ Flat(final double[] positions, final int numDimensions) {
+ super(numDimensions, positions.length / numDimensions);
+ this.positions = positions;
+ }
+
+ @Override
+ public double get(final int i, final int d) {
+ return positions[numDimensions * i + d];
+ }
+ }
+
+ KDTreePositions(final int numDimensions, final int numPoints) {
+ this.numDimensions = numDimensions;
+ this.numPoints = numPoints;
+ }
+
+ /**
+ * Get the coordinates of the node {@code i} in dimension {@code d}.
+ *
+ * @return the coordinate
+ */
+ public abstract double get(final int i, final int d);
+
+ public int numDimensions() {
+ return numDimensions;
+ }
+
+ public int size() {
+ return numPoints;
+ }
+
+ public static KDTreePositions create(final double[][] positions) {
+ return new Nested( positions );
+ }
+
+ public static KDTreePositions create(final double[] positions, final int numDimensions) {
+ return new Flat(positions, numDimensions);
+ }
+}
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeValuesImg.java b/src/main/java/net/imglib2/kdtree/KDTreeValuesImg.java
index 569d8b03e..dddb1be10 100644
--- a/src/main/java/net/imglib2/kdtree/KDTreeValuesImg.java
+++ b/src/main/java/net/imglib2/kdtree/KDTreeValuesImg.java
@@ -32,6 +32,7 @@ public KDTreeValuesImg(RandomAccessibleInterval values) {
/**
* @return number of values in the tree
*/
+ @Override
public int size() {
return (int) values.dimension(0);
}
@@ -39,6 +40,7 @@ public int size() {
/**
* Get the values as a 1D {@code RandomAccessibleInterval}, for serialization.
*/
+ @Override
public RandomAccessibleInterval values() {
return values;
}
@@ -50,6 +52,7 @@ public RandomAccessibleInterval values() {
* apply(i)}} every {@link Supplier#get()} creates a new instance of the
* {@code IntFunction}.
*/
+ @Override
public Supplier> valuesSupplier() {
return valuesSupplier;
}
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeValuesList.java b/src/main/java/net/imglib2/kdtree/KDTreeValuesList.java
index a76ab85a5..cb14c6ee2 100644
--- a/src/main/java/net/imglib2/kdtree/KDTreeValuesList.java
+++ b/src/main/java/net/imglib2/kdtree/KDTreeValuesList.java
@@ -27,6 +27,7 @@ public KDTreeValuesList(List values) {
/**
* @return number of values in the tree
*/
+ @Override
public int size() {
return values.size();
}
@@ -34,6 +35,7 @@ public int size() {
/**
* Get the values as a 1D {@code RandomAccessibleInterval}, for serialization.
*/
+ @Override
public RandomAccessibleInterval values() {
return ListImg.wrap(values, values.size());
}
@@ -45,6 +47,7 @@ public RandomAccessibleInterval values() {
* apply(i)}} every {@link Supplier#get()} creates a new instance of the
* {@code IntFunction}.
*/
+ @Override
public Supplier> valuesSupplier() {
return valuesSupplier;
}
From 1232e56a8259d6671bc4728a42984578334d8987 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Fri, 11 Aug 2023 15:51:28 -0400
Subject: [PATCH 10/13] Store only KDTreePositions in KDTreeData
---
src/main/java/net/imglib2/KDTree.java | 9 +-
.../java/net/imglib2/kdtree/KDTreeData.java | 130 ++----------------
.../java/net/imglib2/kdtree/KDTreeImpl.java | 41 +-----
.../net/imglib2/kdtree/KDTreePositions.java | 97 +++++++++++--
.../net/imglib2/kdtree/KDTreeBenchmark.java | 2 +-
.../net/imglib2/kdtree/KDTreeImplTest.java | 6 +-
6 files changed, 113 insertions(+), 172 deletions(-)
diff --git a/src/main/java/net/imglib2/KDTree.java b/src/main/java/net/imglib2/KDTree.java
index 6a95e53b8..8aae46b91 100644
--- a/src/main/java/net/imglib2/KDTree.java
+++ b/src/main/java/net/imglib2/KDTree.java
@@ -1,15 +1,12 @@
package net.imglib2;
import java.util.List;
-import java.util.function.IntFunction;
-import java.util.function.Supplier;
+
import net.imglib2.converter.AbstractConvertedIterableRealInterval;
import net.imglib2.converter.AbstractConvertedRealCursor;
import net.imglib2.kdtree.KDTreeData;
import net.imglib2.kdtree.KDTreeImpl;
-import static net.imglib2.kdtree.KDTreeData.PositionsLayout.FLAT;
-
public class KDTree< T > implements EuclideanSpace, IterableRealInterval< T >
{
private final KDTreeData< T > treeData;
@@ -132,9 +129,7 @@ public < L extends RealLocalizable > KDTree( final int numPoints, final Iterable
public KDTree( final KDTreeData< T > data )
{
treeData = data;
- impl = ( data.layout() == FLAT )
- ? KDTreeImpl.create( data.flatPositions(), data.numDimensions() )
- : KDTreeImpl.create( data.positions() );
+ impl = new KDTreeImpl(treeData.positions);
}
/**
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeData.java b/src/main/java/net/imglib2/kdtree/KDTreeData.java
index 463ec804c..7635de4f9 100644
--- a/src/main/java/net/imglib2/kdtree/KDTreeData.java
+++ b/src/main/java/net/imglib2/kdtree/KDTreeData.java
@@ -3,62 +3,36 @@
import java.util.List;
import java.util.function.IntFunction;
import java.util.function.Supplier;
-import net.imglib2.FinalRealInterval;
-import net.imglib2.RandomAccess;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.RealInterval;
import net.imglib2.RealLocalizable;
import net.imglib2.img.Img;
-import net.imglib2.img.list.ListImg;
import net.imglib2.type.NativeType;
-import net.imglib2.util.Util;
-import static net.imglib2.kdtree.KDTreeData.PositionsLayout.FLAT;
/**
* Stores the KDTree data, that is, positions and values.
*
- * Positions are stored in either {@code FLAT} or {@code NESTED} {@link
- * PositionsLayout layout}. With {@code NESTED} layout, positions are stored as
- * a nested {@code double[][]} array where {@code positions[d][i]} is dimension
- * {@code d} of the {@code i}-th point. With {@code FLAT} layout, positions are
- * stored as a flat {@code double[]} array, where {@code positions[d + i*n]} is
- * dimension {@code d} of the {@code i}-th point, with {@code n} the number of
- * dimensions.
+ * Positions are stored as {@link KDTreePositions} as either
+ * {@link KDTreePositions.Flat} or {@link KDTreePositions.Nested}.
*
- * Values (of type {@code T}) are stored as either a 1D {@code
- * RandomAccessibleInterval}, or a {@code List}. Individual values can be
- * accessed by {@link #valuesSupplier()}{@code .get().apply(i)}. {@code
- * valueSupplier().get()} returns a reusable {@code IntFunction}. Here {@code
- * T} maybe a proxy that is reused in subsequent {@code apply(i)}.
+ * Values (of type {@code T}) are stored as {@link KDTreeValues} either a
+ * 1D {@code RandomAccessibleInterval} in {@link KDTreeValuesImg}, or
+ * a {@code List} in {@link KDTreeValuesList}.
*
* {@link #values()} returns all values as a 1D {@code
- * RandomAccessibleInterval}. (If data is stored as {@code List}, it is
- * wrapped into a {@code ListImg}.)
- *
- * {@link #positions()} returns positions in nested {@code double[][]} (which is
- * created if internal storage is {@code FLAT}). {@link #flatPositions()}
- * returns flat {@code double[]} if internal storage is {@code FLAT}, otherwise
- * {@code null}.
+ * RandomAccessibleInterval}. (If data is stored as {@link
+ * KDTreeValuesList}, it is wrapped into a {@code ListImg}.)
*
* @param
* the type of values stored in the tree.
*/
public class KDTreeData< T >
{
- public enum PositionsLayout
- {
- FLAT,
- NESTED
- }
-
private final int numDimensions;
private final int numPoints;
- private final PositionsLayout layout;
- private final double[][] positions;
- private final double[] flatPositions;
- private final KDTreePositions newPositions;
+ public final KDTreePositions positions;
private final KDTreeValues values;
@@ -68,12 +42,7 @@ public KDTreeData( double[][] positions, List< T > values )
{
numPoints = values.size();
numDimensions = positions.length;
-
- layout = PositionsLayout.NESTED;
- this.positions = positions;
- flatPositions = null;
- newPositions = new KDTreePositions.Nested(positions);
-
+ this.positions = new KDTreePositions.Nested(positions);
this.values = new KDTreeValuesList<>(values);
}
@@ -87,12 +56,7 @@ public KDTreeData( double[][] positions, RandomAccessibleInterval< T > values )
{
numPoints = ( int ) values.dimension( 0 );
numDimensions = positions.length;
-
- layout = PositionsLayout.NESTED;
- this.positions = positions;
- flatPositions = null;
- newPositions = new KDTreePositions.Nested(positions);
-
+ this.positions = new KDTreePositions.Nested(positions);
this.values = new KDTreeValuesImg<>(values);
}
@@ -106,12 +70,7 @@ public KDTreeData( double[] positions, List< T > values )
{
numPoints = values.size();
numDimensions = positions.length / numPoints;
-
- layout = FLAT;
- this.positions = null;
- flatPositions = positions;
- newPositions = new KDTreePositions.Flat(positions, numDimensions);
-
+ this.positions = new KDTreePositions.Flat(positions, numDimensions);
this.values = new KDTreeValuesList<>(values);
}
@@ -125,12 +84,7 @@ public KDTreeData( double[] positions, RandomAccessibleInterval< T > values )
{
numPoints = ( int ) values.dimension( 0 );
numDimensions = positions.length / numPoints;
-
- layout = FLAT;
- this.positions = null;
- flatPositions = positions;
- newPositions = new KDTreePositions.Flat(positions, numDimensions);
-
+ this.positions = new KDTreePositions.Flat(positions, numDimensions);
this.values = new KDTreeValuesImg<>(values);
}
@@ -146,19 +100,9 @@ public T type()
return values.type();
}
- /**
- * Get positions of points in the tree as a nested {@code double[][]} array
- * where {@code positions[d][i]} is dimension {@code d} of the {@code i}-th
- * point.
- *
- * For serialisation and usage by the tree.
- *
- * Internal storage may be flattened into single {@code double[]} array. In
- * this case, the nested {@code double[][]} array is created here.
- */
- public double[][] positions()
+ public KDTreePositions positions()
{
- return layout == FLAT ? KDTreeUtils.unflatten( flatPositions, numDimensions ) : positions;
+ return positions;
}
/**
@@ -183,37 +127,6 @@ public Supplier< IntFunction< T > > valuesSupplier()
return values.valuesSupplier();
}
- /**
- * Get positions of points in the tree as a flat {@code double[]} where
- * {@code positions[d + i*n]} is dimension {@code d} of the {@code i}-th
- * point, with {@code n} the number of dimensions.
- *
- * For serialisation and usage by the tree.
- *
- * Internal storage may be a {@code NESTED} {@code double[][]} array. In
- * this case, {@code flatPositions()} returns {@code null}.
- */
- public double[] flatPositions()
- {
- return flatPositions;
- }
-
- /**
- * Get the internal layout of positions.
- *
- * Positions are stored in either {@code FLAT} or {@code NESTED} {@link
- * PositionsLayout layout}. With {@code NESTED} layout, positions are stored
- * as a nested {@code double[][]} array where {@code positions[d][i]} is
- * dimension {@code d} of the {@code i}-th point. With {@code FLAT} layout,
- * positions are stored as a flat {@code double[]} array, where {@code
- * positions[d + i*n]} is dimension {@code d} of the {@code i}-th point,
- * with {@code n} the number of dimensions.
- */
- public PositionsLayout layout()
- {
- return layout;
- }
-
/**
* @return dimensionality of points in the tree
*/
@@ -232,20 +145,7 @@ public int size()
public RealInterval boundingBox()
{
- if ( boundingBox == null )
- boundingBox = createBoundingBox();
- return boundingBox;
- }
-
- private RealInterval createBoundingBox()
- {
- final double[] min = new double[ numDimensions ];
- final double[] max = new double[ numDimensions ];
- if ( layout == FLAT )
- KDTreeUtils.computeMinMax( flatPositions, min, max );
- else
- KDTreeUtils.computeMinMax( positions, min, max );
- return FinalRealInterval.wrap( min, max );
+ return (boundingBox != null) ? boundingBox : positions.boundingBox();
}
/**
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeImpl.java b/src/main/java/net/imglib2/kdtree/KDTreeImpl.java
index 9c33cb9e5..5732a1703 100644
--- a/src/main/java/net/imglib2/kdtree/KDTreeImpl.java
+++ b/src/main/java/net/imglib2/kdtree/KDTreeImpl.java
@@ -41,37 +41,18 @@
* without dependent reads. And iff the calculated child index is less
* than the number of nodes, the child exists.
*/
-public abstract class KDTreeImpl
+public class KDTreeImpl
{
final int numDimensions;
private final int numPoints;
- // TODO: make this final
- protected KDTreePositions positions;
+ protected final KDTreePositions positions;
- static class Nested extends KDTreeImpl
- {
- Nested( final double[][] positions )
- {
- super( positions.length, positions[ 0 ].length );
- this.positions = new KDTreePositions.Nested(positions);
- }
- }
-
- static class Flat extends KDTreeImpl
- {
- Flat( final double[] positions, final int numDimensions )
- {
- super( numDimensions, positions.length / numDimensions );
- this.positions = new KDTreePositions.Flat(positions, numDimensions);
- }
- }
-
- KDTreeImpl( final int numDimensions, final int numPoints )
- {
- this.numDimensions = numDimensions;
- this.numPoints = numPoints;
+ public KDTreeImpl(final KDTreePositions positions) {
+ this.numDimensions = positions.numDimensions;
+ this.numPoints = positions.numPoints;
+ this.positions = positions;
}
/**
@@ -205,14 +186,4 @@ public int depth()
{
return 32 - Integer.numberOfLeadingZeros( numPoints );
}
-
- public static KDTreeImpl create( final double[][] positions )
- {
- return new Nested( positions );
- }
-
- public static KDTreeImpl create( final double[] positions, final int numDimensions )
- {
- return new Flat( positions, numDimensions );
- }
}
diff --git a/src/main/java/net/imglib2/kdtree/KDTreePositions.java b/src/main/java/net/imglib2/kdtree/KDTreePositions.java
index 86f3f8ef8..1e9e3911c 100644
--- a/src/main/java/net/imglib2/kdtree/KDTreePositions.java
+++ b/src/main/java/net/imglib2/kdtree/KDTreePositions.java
@@ -1,26 +1,32 @@
package net.imglib2.kdtree;
-import net.imglib2.RealLocalizable;
+import net.imglib2.FinalRealInterval;
+import net.imglib2.RealInterval;
-import static net.imglib2.kdtree.KDTreeUtils.leftChildIndex;
-import static net.imglib2.kdtree.KDTreeUtils.parentIndex;
-import static net.imglib2.kdtree.KDTreeUtils.rightChildIndex;
/**
* Stores the positions of the nodes in a KDTree and provides access to them.
*
* Currently, there are two implementations:
- * - {@link Nested} stores the positions as a {@code double[][]}, allowing a
- * total of 2^31-8 nodes but doesn't keep the positions contiguous in memory.
- * - {@link Flat} stores the positions as a {@code double[]}, meaning that the
- * positions are contiguous in memory but the number of nodes is limited to
- * (2^31-8)/numDimensions.
+ * - {@link Nested} stores the positions as a {@code double[][]} where {@code
+ * positions[d][i]} is dimension {@code d} of the {@code i}-th point. This
+ * allows for a total of 2^31-8 nodes but doesn't keep the positions
+ * contiguous in memory.
+ * - {@link Flat} stores the positions as a {@code double[]}where {@code
+ * positions[d + i*n]} is dimension {@code d} of the {@code i}-th point,
+ * with {@code n} the number of dimensions. This means that the positions
+ * are contiguous in memory but the number of nodes is limited to (2^31-8)/n.
+ *
+ * {@link #getNestedPositions()} returns positions in nested {@code double[][]}
+ * (which is created if class is {@link Flat}). {@link #getFlatPositions()}
+ * returns flat {@code double[]} if class is {@link Flat}, otherwise {@code null}.
*/
public abstract class KDTreePositions {
protected final int numDimensions;
protected final int numPoints;
+ private volatile RealInterval boundingBox;
static class Nested extends KDTreePositions {
private final double[][] positions;
@@ -30,10 +36,28 @@ static class Nested extends KDTreePositions {
this.positions = positions;
}
- @Override
- public double get(final int i, final int d) {
+ @Override public double get(final int i, final int d) {
return positions[d][i];
}
+
+ @Override
+ public double[] getFlatPositions() {
+ // positions in this case might be too large to fit in a single array
+ return null;
+ }
+
+ @Override
+ public double[][] getNestedPositions() {
+ return positions;
+ }
+
+ @Override
+ protected RealInterval createBoundingBox() {
+ final double[] min = new double[numDimensions];
+ final double[] max = new double[numDimensions];
+ KDTreeUtils.computeMinMax(positions, min, max);
+ return FinalRealInterval.wrap(min, max);
+ }
}
static class Flat extends KDTreePositions {
@@ -48,6 +72,24 @@ static class Flat extends KDTreePositions {
public double get(final int i, final int d) {
return positions[numDimensions * i + d];
}
+
+ @Override
+ public double[] getFlatPositions() {
+ return positions;
+ }
+
+ @Override
+ public double[][] getNestedPositions() {
+ return KDTreeUtils.unflatten(positions, numDimensions);
+ }
+
+ @Override
+ protected RealInterval createBoundingBox() {
+ final double[] min = new double[numDimensions];
+ final double[] max = new double[numDimensions];
+ KDTreeUtils.computeMinMax(positions, min, max);
+ return FinalRealInterval.wrap(min, max);
+ }
}
KDTreePositions(final int numDimensions, final int numPoints) {
@@ -62,6 +104,33 @@ public double get(final int i, final int d) {
*/
public abstract double get(final int i, final int d);
+ /**
+ * Get positions of points in the tree as a flat {@code double[]} array
+ * where {@code positions[d + i*n]} is dimension {@code d} of the {@code i}-th
+ * point.
+ *
+ * For serialisation and usage by the tree.
+ *
+ * Internal storage may be nested in a {@code double[][]} array. In
+ * this case, there may be too many points to fit in a single {@code
+ * double[]} array, so {@code null} is returned.
+ */
+ public abstract double[] getFlatPositions();
+
+ /**
+ * Get positions of points in the tree as a nested {@code double[][]} array
+ * where {@code positions[d][i]} is dimension {@code d} of the {@code i}-th
+ * point.
+ *
+ * For serialisation and usage by the tree.
+ *
+ * Internal storage may be flattened into single {@code double[]} array. In
+ * this case, the nested {@code double[][]} array is created here.
+ */
+ public abstract double[][] getNestedPositions();
+
+ protected abstract RealInterval createBoundingBox();
+
public int numDimensions() {
return numDimensions;
}
@@ -70,6 +139,12 @@ public int size() {
return numPoints;
}
+ public RealInterval boundingBox() {
+ if (boundingBox == null)
+ boundingBox = createBoundingBox();
+ return boundingBox;
+ }
+
public static KDTreePositions create(final double[][] positions) {
return new Nested( positions );
}
diff --git a/src/test/java/net/imglib2/kdtree/KDTreeBenchmark.java b/src/test/java/net/imglib2/kdtree/KDTreeBenchmark.java
index 79c4e8fdd..d7eef7c88 100644
--- a/src/test/java/net/imglib2/kdtree/KDTreeBenchmark.java
+++ b/src/test/java/net/imglib2/kdtree/KDTreeBenchmark.java
@@ -61,7 +61,7 @@ public void spoil() {
final double[][] points = KDTreeUtils.initPositions( n, numDataVertices, dataVertices );
final int[] tree = KDTreeUtils.makeTree( points );
final double[][] treePoints = KDTreeUtils.reorder( points, tree );
- final KDTreeImpl impl = new KDTreeImpl.Nested( treePoints );
+ final KDTreeImpl impl = new KDTreeImpl(new KDTreePositions.Nested(treePoints));
final NearestNeighborSearchImpl search = new NearestNeighborSearchImpl( impl );
for ( RealPoint testVertex : testVertices )
search.search( testVertex );
diff --git a/src/test/java/net/imglib2/kdtree/KDTreeImplTest.java b/src/test/java/net/imglib2/kdtree/KDTreeImplTest.java
index d3095f98f..e683320a8 100644
--- a/src/test/java/net/imglib2/kdtree/KDTreeImplTest.java
+++ b/src/test/java/net/imglib2/kdtree/KDTreeImplTest.java
@@ -52,7 +52,7 @@ public void testNearestNeighborSearch()
final double[][] points = KDTreeUtils.initPositions( n, numDataVertices, dataVertices );
final int[] tree = KDTreeUtils.makeTree( points );
final double[][] treePoints = KDTreeUtils.reorder( points, tree );
- final KDTreeImpl impl = new KDTreeImpl.Nested( treePoints );
+ final KDTreeImpl impl = new KDTreeImpl(new KDTreePositions.Nested(treePoints));
final NearestNeighborSearchImpl search = new NearestNeighborSearchImpl( impl );
for ( RealPoint testVertex : testVertices )
@@ -75,7 +75,7 @@ public void testKNearestNeighborSearch()
final double[][] points = KDTreeUtils.initPositions( n, numDataVertices, dataVertices );
final int[] tree = KDTreeUtils.makeTree( points );
final double[][] treePoints = KDTreeUtils.reorder( points, tree );
- final KDTreeImpl impl = new KDTreeImpl.Nested( treePoints );
+ final KDTreeImpl impl = new KDTreeImpl(new KDTreePositions.Nested(treePoints));
final KNearestNeighborSearchImpl search = new KNearestNeighborSearchImpl( impl, k );
for ( RealPoint testVertex : testVertices )
@@ -99,7 +99,7 @@ public void testRadiusNeighborSearch()
final double[][] points = KDTreeUtils.initPositions( n, numDataVertices, dataVertices );
final int[] tree = KDTreeUtils.makeTree( points );
final double[][] treePoints = KDTreeUtils.reorder( points, tree );
- final KDTreeImpl impl = new KDTreeImpl.Nested( treePoints );
+ final KDTreeImpl impl = new KDTreeImpl(new KDTreePositions.Nested(treePoints));
final RadiusNeighborSearchImpl search = new RadiusNeighborSearchImpl( impl );
for ( RealPoint testVertex : testVertices )
From ac6cf63f96103e6874921725a4415f2e0bae3830 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Fri, 11 Aug 2023 17:07:00 -0400
Subject: [PATCH 11/13] Add some convenience functions used in serialization
---
src/main/java/net/imglib2/kdtree/KDTreeData.java | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeData.java b/src/main/java/net/imglib2/kdtree/KDTreeData.java
index 7635de4f9..e874aa83d 100644
--- a/src/main/java/net/imglib2/kdtree/KDTreeData.java
+++ b/src/main/java/net/imglib2/kdtree/KDTreeData.java
@@ -105,6 +105,18 @@ public KDTreePositions positions()
return positions;
}
+ public double[] getFlatPositions() {
+ return positions.getFlatPositions();
+ }
+
+ public double[][] getNestedPositions() {
+ return positions.getNestedPositions();
+ }
+
+ public boolean positionsIsFlatArray() {
+ return (positions instanceof KDTreePositions.Flat);
+ }
+
/**
* Get the values as a 1D {@code RandomAccessibleInterval}, for
* serialization. (If the underlying storage is a {@code List}, it will
From 2368e5c7a35fc0dfc5e7bd4d6f3c1edcdb445998 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Fri, 11 Aug 2023 20:19:09 -0400
Subject: [PATCH 12/13] Consolidate KDTreeValues
---
.../java/net/imglib2/kdtree/KDTreeData.java | 14 ++--
.../java/net/imglib2/kdtree/KDTreeValues.java | 81 +++++++++++++++++++
.../net/imglib2/kdtree/KDTreeValuesImg.java | 59 --------------
.../net/imglib2/kdtree/KDTreeValuesList.java | 54 -------------
4 files changed, 88 insertions(+), 120 deletions(-)
delete mode 100644 src/main/java/net/imglib2/kdtree/KDTreeValuesImg.java
delete mode 100644 src/main/java/net/imglib2/kdtree/KDTreeValuesList.java
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeData.java b/src/main/java/net/imglib2/kdtree/KDTreeData.java
index e874aa83d..80bb52b99 100644
--- a/src/main/java/net/imglib2/kdtree/KDTreeData.java
+++ b/src/main/java/net/imglib2/kdtree/KDTreeData.java
@@ -17,12 +17,12 @@
* {@link KDTreePositions.Flat} or {@link KDTreePositions.Nested}.
*
* Values (of type {@code T}) are stored as {@link KDTreeValues} either a
- * 1D {@code RandomAccessibleInterval} in {@link KDTreeValuesImg}, or
- * a {@code List} in {@link KDTreeValuesList}.
+ * 1D {@code RandomAccessibleInterval} in {@link KDTreeValues.ImgValues}, or
+ * a {@code List} in {@link KDTreeValues.ListValues}.
*
* {@link #values()} returns all values as a 1D {@code
* RandomAccessibleInterval}. (If data is stored as {@link
- * KDTreeValuesList}, it is wrapped into a {@code ListImg}.)
+ * KDTreeValues.ListValues}, it is wrapped into a {@code ListImg}.)
*
* @param
* the type of values stored in the tree.
@@ -43,7 +43,7 @@ public KDTreeData( double[][] positions, List< T > values )
numPoints = values.size();
numDimensions = positions.length;
this.positions = new KDTreePositions.Nested(positions);
- this.values = new KDTreeValuesList<>(values);
+ this.values = new KDTreeValues.ListValues<>(values);
}
public KDTreeData( double[][] positions, List< T > values, RealInterval boundingBox )
@@ -57,7 +57,7 @@ public KDTreeData( double[][] positions, RandomAccessibleInterval< T > values )
numPoints = ( int ) values.dimension( 0 );
numDimensions = positions.length;
this.positions = new KDTreePositions.Nested(positions);
- this.values = new KDTreeValuesImg<>(values);
+ this.values = new KDTreeValues.ImgValues<>(values);
}
public KDTreeData( double[][] positions, RandomAccessibleInterval< T > values, RealInterval boundingBox )
@@ -71,7 +71,7 @@ public KDTreeData( double[] positions, List< T > values )
numPoints = values.size();
numDimensions = positions.length / numPoints;
this.positions = new KDTreePositions.Flat(positions, numDimensions);
- this.values = new KDTreeValuesList<>(values);
+ this.values = new KDTreeValues.ListValues<>(values);
}
public KDTreeData( double[] positions, List< T > values, RealInterval boundingBox )
@@ -85,7 +85,7 @@ public KDTreeData( double[] positions, RandomAccessibleInterval< T > values )
numPoints = ( int ) values.dimension( 0 );
numDimensions = positions.length / numPoints;
this.positions = new KDTreePositions.Flat(positions, numDimensions);
- this.values = new KDTreeValuesImg<>(values);
+ this.values = new KDTreeValues.ImgValues<>(values);
}
public KDTreeData( double[] positions, RandomAccessibleInterval< T > values, RealInterval boundingBox )
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeValues.java b/src/main/java/net/imglib2/kdtree/KDTreeValues.java
index ab66189ac..74c476657 100644
--- a/src/main/java/net/imglib2/kdtree/KDTreeValues.java
+++ b/src/main/java/net/imglib2/kdtree/KDTreeValues.java
@@ -1,7 +1,11 @@
package net.imglib2.kdtree;
+import net.imglib2.RandomAccess;
import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.img.list.ListImg;
+import net.imglib2.util.Util;
+import java.util.List;
import java.util.function.IntFunction;
import java.util.function.Supplier;
@@ -24,6 +28,75 @@
abstract public class KDTreeValues {
protected final T type;
+ /**
+ * Stores KDTree values as 1D {@code RandomAccessibleInterval}.
+ *
+ * @param
+ * the type of values stored in the tree.
+ */
+ public static class ImgValues extends KDTreeValues {
+
+ private final RandomAccessibleInterval values;
+ private final Supplier> valuesSupplier;
+
+ public ImgValues(RandomAccessibleInterval values) {
+ super(Util.getTypeFromInterval(values));
+ this.values = values;
+ valuesSupplier = () -> {
+ final RandomAccess ra = values.randomAccess();
+ return ra::setPositionAndGet;
+ };
+ }
+
+ @Override
+ public int size() {
+ return (int) values.dimension(0);
+ }
+
+ @Override
+ public RandomAccessibleInterval values() {
+ return values;
+ }
+
+ @Override
+ public Supplier> valuesSupplier() {
+ return valuesSupplier;
+ }
+ }
+
+ /**
+ * Stores KDTree values as List.
+ *
+ * @param
+ * the type of values stored in the tree.
+ */
+ public static class ListValues extends KDTreeValues {
+
+ private final List values;
+ private final Supplier> valuesSupplier;
+
+ public ListValues(List values) {
+ super(KDTreeUtils.getType(values));
+ this.values = values;
+ valuesSupplier = () -> values::get;
+ }
+
+ @Override
+ public int size() {
+ return values.size();
+ }
+
+ @Override
+ public RandomAccessibleInterval values() {
+ return ListImg.wrap(values, values.size());
+ }
+
+ @Override
+ public Supplier> valuesSupplier() {
+ return valuesSupplier;
+ }
+ }
+
public KDTreeValues(T type) {
this.type = type;
}
@@ -50,4 +123,12 @@ public T type() {
* {@code IntFunction}.
*/
abstract public Supplier> valuesSupplier();
+
+ public static KDTreeValues create(final RandomAccessibleInterval values) {
+ return new KDTreeValues.ImgValues<>(values);
+ }
+
+ public static KDTreeValues create(final List values) {
+ return new KDTreeValues.ListValues<>(values);
+ }
}
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeValuesImg.java b/src/main/java/net/imglib2/kdtree/KDTreeValuesImg.java
deleted file mode 100644
index dddb1be10..000000000
--- a/src/main/java/net/imglib2/kdtree/KDTreeValuesImg.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package net.imglib2.kdtree;
-
-import net.imglib2.RandomAccess;
-import net.imglib2.RandomAccessibleInterval;
-import net.imglib2.img.list.ListImg;
-import net.imglib2.util.Util;
-
-import java.util.List;
-import java.util.function.IntFunction;
-import java.util.function.Supplier;
-
-/**
- * Stores KDTree values as 1D {@code RandomAccessibleInterval}.
- *
- * @param
- * the type of values stored in the tree.
- */
-public class KDTreeValuesImg extends KDTreeValues {
-
- private final RandomAccessibleInterval values;
- private final Supplier> valuesSupplier;
-
- public KDTreeValuesImg(RandomAccessibleInterval values) {
- super(Util.getTypeFromInterval(values));
- this.values = values;
- valuesSupplier = () -> {
- final RandomAccess ra = values.randomAccess();
- return ra::setPositionAndGet;
- };
- }
-
- /**
- * @return number of values in the tree
- */
- @Override
- public int size() {
- return (int) values.dimension(0);
- }
-
- /**
- * Get the values as a 1D {@code RandomAccessibleInterval}, for serialization.
- */
- @Override
- public RandomAccessibleInterval values() {
- return values;
- }
-
- /**
- * Get a {@code Supplier} that return {@code IntFunction} to provide
- * values for a given node indices. If the returned {@code IntFunction}
- * is stateful ({@code T} maybe a proxy that is reused in subsequent {@code
- * apply(i)}} every {@link Supplier#get()} creates a new instance of the
- * {@code IntFunction}.
- */
- @Override
- public Supplier> valuesSupplier() {
- return valuesSupplier;
- }
-}
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeValuesList.java b/src/main/java/net/imglib2/kdtree/KDTreeValuesList.java
deleted file mode 100644
index cb14c6ee2..000000000
--- a/src/main/java/net/imglib2/kdtree/KDTreeValuesList.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package net.imglib2.kdtree;
-
-import net.imglib2.RandomAccessibleInterval;
-import net.imglib2.img.list.ListImg;
-
-import java.util.List;
-import java.util.function.IntFunction;
-import java.util.function.Supplier;
-
-/**
- * Stores KDTree values as List.
- *
- * @param
- * the type of values stored in the tree.
- */
-public class KDTreeValuesList extends KDTreeValues {
-
- private final List values;
- private final Supplier> valuesSupplier;
-
- public KDTreeValuesList(List values) {
- super(KDTreeUtils.getType(values));
- this.values = values;
- valuesSupplier = () -> values::get;
- }
-
- /**
- * @return number of values in the tree
- */
- @Override
- public int size() {
- return values.size();
- }
-
- /**
- * Get the values as a 1D {@code RandomAccessibleInterval}, for serialization.
- */
- @Override
- public RandomAccessibleInterval values() {
- return ListImg.wrap(values, values.size());
- }
-
- /**
- * Get a {@code Supplier} that return {@code IntFunction} to provide
- * values for a given node indices. If the returned {@code IntFunction}
- * is stateful ({@code T} maybe a proxy that is reused in subsequent {@code
- * apply(i)}} every {@link Supplier#get()} creates a new instance of the
- * {@code IntFunction}.
- */
- @Override
- public Supplier> valuesSupplier() {
- return valuesSupplier;
- }
-}
From afb0ff934d47bb6f0c93d13fad293191f87bde43 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Sat, 12 Aug 2023 15:59:56 -0400
Subject: [PATCH 13/13] Decouple creation logic of positions and values
---
.../java/net/imglib2/kdtree/KDTreeData.java | 94 ++++---------------
.../net/imglib2/kdtree/KDTreePositions.java | 4 +
2 files changed, 23 insertions(+), 75 deletions(-)
diff --git a/src/main/java/net/imglib2/kdtree/KDTreeData.java b/src/main/java/net/imglib2/kdtree/KDTreeData.java
index 80bb52b99..db84eccdd 100644
--- a/src/main/java/net/imglib2/kdtree/KDTreeData.java
+++ b/src/main/java/net/imglib2/kdtree/KDTreeData.java
@@ -1,6 +1,5 @@
package net.imglib2.kdtree;
-import java.util.List;
import java.util.function.IntFunction;
import java.util.function.Supplier;
import net.imglib2.RandomAccessibleInterval;
@@ -38,59 +37,15 @@ public class KDTreeData< T >
private volatile RealInterval boundingBox;
- public KDTreeData( double[][] positions, List< T > values )
- {
- numPoints = values.size();
- numDimensions = positions.length;
- this.positions = new KDTreePositions.Nested(positions);
- this.values = new KDTreeValues.ListValues<>(values);
- }
-
- public KDTreeData( double[][] positions, List< T > values, RealInterval boundingBox )
- {
- this( positions, values );
- this.boundingBox = boundingBox;
- }
-
- public KDTreeData( double[][] positions, RandomAccessibleInterval< T > values )
- {
- numPoints = ( int ) values.dimension( 0 );
- numDimensions = positions.length;
- this.positions = new KDTreePositions.Nested(positions);
- this.values = new KDTreeValues.ImgValues<>(values);
- }
-
- public KDTreeData( double[][] positions, RandomAccessibleInterval< T > values, RealInterval boundingBox )
- {
- this( positions, values );
- this.boundingBox = boundingBox;
- }
-
- public KDTreeData( double[] positions, List< T > values )
- {
- numPoints = values.size();
- numDimensions = positions.length / numPoints;
- this.positions = new KDTreePositions.Flat(positions, numDimensions);
- this.values = new KDTreeValues.ListValues<>(values);
- }
-
- public KDTreeData( double[] positions, List< T > values, RealInterval boundingBox )
- {
- this( positions, values );
- this.boundingBox = boundingBox;
- }
-
- public KDTreeData( double[] positions, RandomAccessibleInterval< T > values )
- {
- numPoints = ( int ) values.dimension( 0 );
- numDimensions = positions.length / numPoints;
- this.positions = new KDTreePositions.Flat(positions, numDimensions);
- this.values = new KDTreeValues.ImgValues<>(values);
+ public KDTreeData(KDTreePositions positions, KDTreeValues values) {
+ numPoints = positions.numPoints();
+ numDimensions = positions.numDimensions();
+ this.positions = positions;
+ this.values = values;
}
- public KDTreeData( double[] positions, RandomAccessibleInterval< T > values, RealInterval boundingBox )
- {
- this( positions, values );
+ public KDTreeData(KDTreePositions positions, KDTreeValues values, RealInterval boundingBox) {
+ this(positions, values);
this.boundingBox = boundingBox;
}
@@ -100,11 +55,6 @@ public T type()
return values.type();
}
- public KDTreePositions positions()
- {
- return positions;
- }
-
public double[] getFlatPositions() {
return positions.getFlatPositions();
}
@@ -188,23 +138,17 @@ public static < L extends RealLocalizable, T > KDTreeData< T > create(
final int[] tree = KDTreeUtils.makeTree( points );
final int[] invtree = KDTreeUtils.invert( tree );
- final boolean useFlatLayout = ( long ) numDimensions * numPoints <= KDTreeUtils.MAX_ARRAY_SIZE;
- if ( storeValuesAsNativeImg && KDTreeUtils.getType( values ) instanceof NativeType )
- {
- @SuppressWarnings( "unchecked" )
- final Img< T > treeValues = ( Img< T > ) KDTreeUtils.orderValuesImg( invtree, ( Iterable ) values );
- if ( useFlatLayout )
- return new KDTreeData<>(KDTreeUtils.reorderToFlatLayout( points, tree ), treeValues);
- else
- return new KDTreeData<>(KDTreeUtils.reorder( points, tree ), treeValues);
- }
- else
- {
- final List< T > treeValues = KDTreeUtils.orderValuesList( invtree, values );
- if ( useFlatLayout )
- return new KDTreeData<>(KDTreeUtils.reorderToFlatLayout( points, tree ), treeValues);
- else
- return new KDTreeData<>(KDTreeUtils.reorder( points, tree ), treeValues);
- }
+ final boolean storeAsImg = (storeValuesAsNativeImg && KDTreeUtils.getType(values) instanceof NativeType);
+ @SuppressWarnings("unchecked")
+ final KDTreeValues treeValues = (storeAsImg)
+ ? new KDTreeValues.ImgValues<>((Img) KDTreeUtils.orderValuesImg(invtree, (Iterable) values))
+ : new KDTreeValues.ListValues<>(KDTreeUtils.orderValuesList(invtree, values));
+
+ final boolean useFlatLayout = (long) numDimensions * numPoints <= KDTreeUtils.MAX_ARRAY_SIZE;
+ final KDTreePositions treePositions = (useFlatLayout)
+ ? new KDTreePositions.Flat(KDTreeUtils.reorderToFlatLayout(points, tree), numDimensions)
+ : new KDTreePositions.Nested(KDTreeUtils.reorder(points, tree));
+
+ return new KDTreeData<>(treePositions, treeValues);
}
}
diff --git a/src/main/java/net/imglib2/kdtree/KDTreePositions.java b/src/main/java/net/imglib2/kdtree/KDTreePositions.java
index 1e9e3911c..8d8acc6db 100644
--- a/src/main/java/net/imglib2/kdtree/KDTreePositions.java
+++ b/src/main/java/net/imglib2/kdtree/KDTreePositions.java
@@ -135,6 +135,10 @@ public int numDimensions() {
return numDimensions;
}
+ public int numPoints() {
+ return numPoints;
+ }
+
public int size() {
return numPoints;
}