diff --git a/src/main/java/net/imglib2/algorithm/fill/Filter.java b/src/main/java/net/imglib2/algorithm/fill/Filter.java index cceb89b26..3bf434ffb 100644 --- a/src/main/java/net/imglib2/algorithm/fill/Filter.java +++ b/src/main/java/net/imglib2/algorithm/fill/Filter.java @@ -35,8 +35,8 @@ package net.imglib2.algorithm.fill; /** - * Interface for comparing {@link T} t and {@link U} u and accepting - * them as equivalent in a sense specified by implementation thereof. + * Interface for comparing {@link T} t and {@link U} u and accepting them as + * equivalent in a sense specified by implementation thereof. * * @author Philipp Hanslovsky * @author Stephan Saalfeld @@ -44,6 +44,7 @@ * @param * @param */ +@Deprecated public interface Filter< T, U > { boolean accept( T t, U u ); diff --git a/src/main/java/net/imglib2/algorithm/fill/FloodFill.java b/src/main/java/net/imglib2/algorithm/fill/FloodFill.java index cd0561540..04a48de70 100644 --- a/src/main/java/net/imglib2/algorithm/fill/FloodFill.java +++ b/src/main/java/net/imglib2/algorithm/fill/FloodFill.java @@ -34,6 +34,9 @@ package net.imglib2.algorithm.fill; +import java.util.function.BiPredicate; +import java.util.function.Consumer; + import gnu.trove.list.TLongList; import gnu.trove.list.array.TLongArrayList; import net.imglib2.Cursor; @@ -58,6 +61,182 @@ public class FloodFill // int or long? current TLongList cannot store more than Integer.MAX_VALUE private static final int CLEANUP_THRESHOLD = ( int ) 1e5; + /** + * Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting + * at seed location, write fillLabel into target at current location and + * continue for each pixel in neighborhood defined by shape if neighborhood + * pixel is in the same connected component and fillLabel has not been + * written into that location yet (comparator evaluates to 0). + * + * Convenience call to + * {@link FloodFill#fill(RandomAccessible, RandomAccessible, Localizable, Object, Type, Shape, BiPredicate)}. + * seedLabel is extracted from source at seed location. + * + * @param source + * input + * @param target + * {@link RandomAccessible} to be written into. May be the same + * as input. + * @param seed + * Start flood fill at this location. + * @param fillLabel + * Immutable. Value to be written into valid flood fill + * locations. + * @param shape + * Defines neighborhood that is considered for connected + * components, e.g. + * {@link net.imglib2.algorithm.neighborhood.DiamondShape} + * @param + * input pixel type + * @param + * fill label type + */ + public static < T extends Type< T >, U extends Type< U > > void fill( + final RandomAccessible< T > source, + final RandomAccessible< U > target, + final Localizable seed, + final U fillLabel, + final Shape shape ) + { + final RandomAccess< T > access = source.randomAccess(); + access.setPosition( seed ); + final T seedValue = access.get().copy(); + final BiPredicate< T, U > filter = ( t, u ) -> t.valueEquals( seedValue ) && !u.valueEquals( fillLabel ); + fill( source, target, seed, fillLabel, shape, filter ); + } + + /** + * Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting + * at seed location, write fillLabel into target at current location and + * continue for each pixel in neighborhood defined by shape if neighborhood + * pixel is in the same connected component and fillLabel has not been + * written into that location yet (comparator evaluates to 0). + * + * Convenience call to + * {@link FloodFill#fill(RandomAccessible, RandomAccessible, Localizable, Object, Object, Shape, BiPredicate, Consumer)} + * with {@link TypeWriter} as writer. + * + * @param source + * input + * @param target + * {@link RandomAccessible} to be written into. May be the same + * as input. + * @param seed + * Start flood fill at this location. + * @param fillLabel + * Immutable. Value to be written into valid flood fill + * locations. + * @param shape + * Defines neighborhood that is considered for connected + * components, e.g. + * {@link net.imglib2.algorithm.neighborhood.DiamondShape} + * @param filter + * Returns true if pixel has not been visited yet and should be + * written into. Returns false if target pixel has been visited + * or source pixel is not part of the same connected component. + * @param + * input pixel type + * @param + * fill label type + */ + public static < T, U extends Type< U > > void fill( + final RandomAccessible< T > source, + final RandomAccessible< U > target, + final Localizable seed, + final U fillLabel, + final Shape shape, + final BiPredicate< T, U > filter ) + { + fill( source, target, seed, shape, filter, targetPixel -> targetPixel.set( fillLabel ) ); + } + + /** + * + * Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting + * at seed location, write fillLabel into target at current location and + * continue for each pixel in neighborhood defined by shape if neighborhood + * pixel is in the same connected component and fillLabel has not been + * written into that location yet (comparator evaluates to 0). + * + * @param source + * input + * @param target + * {@link RandomAccessible} to be written into. May be the same + * as input. + * @param seed + * Start flood fill at this location. + * @param shape + * Defines neighborhood that is considered for connected + * components, e.g. + * {@link net.imglib2.algorithm.neighborhood.DiamondShape} + * @param filter + * Returns true if pixel has not been visited yet and should be + * written into. Returns false if target pixel has been visited + * or source pixel is not part of the same connected component. + * @param writer + * Defines how fill label is written into target at current + * location. + * @param + * input pixel type + * @param + * fill label type + */ + public static < T, U > void fill( + final RandomAccessible< T > source, + final RandomAccessible< U > target, + final Localizable seed, + final Shape shape, + final BiPredicate< T, U > filter, + final Consumer< U > writer ) + { + final int n = source.numDimensions(); + + final RandomAccessible< Pair< T, U > > paired = Views.pair( source, target ); + + TLongList coordinates = new TLongArrayList(); + for ( int d = 0; d < n; ++d ) + { + coordinates.add( seed.getLongPosition( d ) ); + } + + final int cleanupThreshold = n * CLEANUP_THRESHOLD; + + final RandomAccessible< Neighborhood< Pair< T, U > > > neighborhood = shape.neighborhoodsRandomAccessible( paired ); + final RandomAccess< Neighborhood< Pair< T, U > > > neighborhoodAccess = neighborhood.randomAccess(); + + final RandomAccess< U > targetAccess = target.randomAccess(); + targetAccess.setPosition( seed ); + writer.accept( targetAccess.get() ); + + for ( int i = 0; i < coordinates.size(); i += n ) + { + for ( int d = 0; d < n; ++d ) + neighborhoodAccess.setPosition( coordinates.get( i + d ), d ); + + final Cursor< Pair< T, U > > neighborhoodCursor = neighborhoodAccess.get().cursor(); + + while ( neighborhoodCursor.hasNext() ) + { + final Pair< T, U > p = neighborhoodCursor.next(); + if ( filter.test( p.getA(), p.getB() ) ) + { + writer.accept( p.getB() ); + for ( int d = 0; d < n; ++d ) + coordinates.add( neighborhoodCursor.getLongPosition( d ) ); + } + } + + if ( i > cleanupThreshold ) + { + // TODO should it start from i + n? + coordinates = coordinates.subList( i, coordinates.size() ); + i = 0; + } + + } + + } + /** * Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting * at seed location, write fillLabel into target at current location and @@ -84,10 +263,11 @@ public class FloodFill * written into. Returns false if target pixel has been visited * or source pixel is not part of the same connected component. * @param - * T implements {@code Type}. + * input pixel type * @param - * U implements {@code Type}. + * fill label type */ + @Deprecated public static < T extends Type< T >, U extends Type< U > > void fill( final RandomAccessible< T > source, final RandomAccessible< U > target, final Localizable seed, final U fillLabel, final Shape shape, final Filter< Pair< T, U >, Pair< T, U > > filter ) { final RandomAccess< T > access = source.randomAccess(); @@ -123,10 +303,11 @@ public static < T extends Type< T >, U extends Type< U > > void fill( final Rand * written into. Returns false if target pixel has been visited * or source pixel is not part of the same connected component. * @param - * No restrictions on {@link T}. + * input pixel type * @param - * {@link U} implements {@code Type}. + * fill label type */ + @Deprecated public static < T, U extends Type< U > > void fill( final RandomAccessible< T > source, final RandomAccessible< U > target, final Localizable seed, final T seedLabel, final U fillLabel, final Shape shape, final Filter< Pair< T, U >, Pair< T, U > > filter ) { fill( source, target, seed, seedLabel, fillLabel, shape, filter, new TypeWriter< U >() ); @@ -164,17 +345,16 @@ public static < T, U extends Type< U > > void fill( final RandomAccessible< T > * Defines how fillLabel is written into target at current * location. * @param - * No restrictions on T. Appropriate comparator is the only - * requirement. + * input pixel type * @param - * No restrictions on U. Appropriate comparator and writer is the - * only requirement. + * fill label type */ + @Deprecated public static < T, U > void fill( final RandomAccessible< T > source, final RandomAccessible< U > target, final Localizable seed, final T seedLabel, final U fillLabel, final Shape shape, final Filter< Pair< T, U >, Pair< T, U > > filter, final Writer< U > writer ) { final int n = source.numDimensions(); - final ValuePair< T, U > reference = new ValuePair< T, U >( seedLabel, fillLabel ); + final ValuePair< T, U > reference = new ValuePair<>( seedLabel, fillLabel ); final RandomAccessible< Pair< T, U > > paired = Views.pair( source, target ); diff --git a/src/main/java/net/imglib2/algorithm/fill/TypeWriter.java b/src/main/java/net/imglib2/algorithm/fill/TypeWriter.java index 6492a6256..4ac8473d9 100644 --- a/src/main/java/net/imglib2/algorithm/fill/TypeWriter.java +++ b/src/main/java/net/imglib2/algorithm/fill/TypeWriter.java @@ -42,6 +42,7 @@ * @author Philipp Hanslovsky * @author Stephan Saalfeld */ +@Deprecated public class TypeWriter< T extends Type< T > > implements Writer< T > { @Override diff --git a/src/main/java/net/imglib2/algorithm/fill/Writer.java b/src/main/java/net/imglib2/algorithm/fill/Writer.java index aa30b004f..4d1ed8100 100644 --- a/src/main/java/net/imglib2/algorithm/fill/Writer.java +++ b/src/main/java/net/imglib2/algorithm/fill/Writer.java @@ -42,6 +42,7 @@ * * @param */ +@Deprecated public interface Writer< U > { void write( final U source, final U target ); diff --git a/src/test/java/net/imglib2/algorithm/fill/FloodFillTest.java b/src/test/java/net/imglib2/algorithm/fill/FloodFillTest.java index 70512cd55..91fcd55c4 100644 --- a/src/test/java/net/imglib2/algorithm/fill/FloodFillTest.java +++ b/src/test/java/net/imglib2/algorithm/fill/FloodFillTest.java @@ -61,7 +61,7 @@ public class FloodFillTest private static final int[] N_DIMS = { 1, 2, 3, 4 }; - private static final int SIZE_OF_EACH_DIM = 60; + private static final int SIZE_OF_EACH_DIM = 24; private static < T extends IntegerType< T > > void runTest( final int nDim, final int sizeOfEachDim, final ImgFactory< T > imageFactory, final T t ) { @@ -112,14 +112,66 @@ else if ( i.getLongPosition( 0 ) - c[ 0 ] > divisionLine ) final ExtendedRandomAccessibleInterval< T, Img< T > > extendedImg = Views.extendValue( img, fillLabel ); - final Filter< Pair< T, T >, Pair< T, T > > filter = new Filter< Pair< T, T >, Pair< T, T > >() + FloodFill.fill( extendedImg, extendedImg, new Point( c ), fillLabel, new DiamondShape( 1 ) ); + + for ( Cursor< T > imgCursor = img.cursor(), refCursor = refImg.cursor(); imgCursor.hasNext(); ) + { + Assert.assertEquals( refCursor.next(), imgCursor.next() ); + } + + } + + @Deprecated + private static < T extends IntegerType< T > > void runTestDeprecated( final int nDim, final int sizeOfEachDim, final ImgFactory< T > imageFactory, final T t ) + { + final long[] dim = new long[ nDim ]; + final long[] c = new long[ nDim ]; + final long r = sizeOfEachDim / 4; + for ( int d = 0; d < nDim; ++d ) { - @Override - public boolean accept( final Pair< T, T > p1, final Pair< T, T > p2 ) + dim[ d ] = sizeOfEachDim; + c[ d ] = sizeOfEachDim / 3; + } + + final long divisionLine = r / 3; + + final Img< T > img = imageFactory.create( dim, t.copy() ); + final Img< T > refImg = imageFactory.create( dim, t.copy() ); + + for ( Cursor< T > i = img.cursor(), ref = refImg.cursor(); i.hasNext(); ) + { + i.fwd(); + ref.fwd(); + long diffSum = 0; + for ( int d = 0; d < nDim; ++d ) { - return ( p1.getB().getIntegerLong() != p2.getB().getIntegerLong() ) && ( p1.getA().getIntegerLong() == p2.getA().getIntegerLong() ); + final long diff = i.getLongPosition( d ) - c[ d ]; + diffSum += diff * diff; + + } + + if ( ( diffSum < r * r ) ) + { + if ( ( i.getLongPosition( 0 ) - c[ 0 ] < divisionLine ) ) + { + i.get().setInteger( START_LABEL ); + ref.get().setInteger( FILL_LABEL ); + } + else if ( i.getLongPosition( 0 ) - c[ 0 ] > divisionLine ) + { + i.get().setInteger( START_LABEL ); + ref.get().setInteger( START_LABEL ); + } } - }; + + } + + final T fillLabel = t.createVariable(); + fillLabel.setInteger( FILL_LABEL ); + + final ExtendedRandomAccessibleInterval< T, Img< T > > extendedImg = Views.extendValue( img, fillLabel ); + + final Filter< Pair< T, T >, Pair< T, T > > filter = ( p1, p2 ) -> ( p1.getB().getIntegerLong() != p2.getB().getIntegerLong() ) && ( p1.getA().getIntegerLong() == p2.getA().getIntegerLong() ); FloodFill.fill( extendedImg, extendedImg, new Point( c ), fillLabel, new DiamondShape( 1 ), filter ); @@ -135,7 +187,8 @@ public void runTests() { for ( final int nDim : N_DIMS ) { - runTest( nDim, SIZE_OF_EACH_DIM, new ArrayImgFactory< LongType >(), new LongType() ); + runTestDeprecated( nDim, SIZE_OF_EACH_DIM, new ArrayImgFactory<>(), new LongType() ); + runTest( nDim, SIZE_OF_EACH_DIM, new ArrayImgFactory<>(), new LongType() ); } }